Vec<T> in Depth

first() and last()

Through deref coercion to [T], Vec<T> provides .first(), .first_mut(), .last(), .last_mut(), .split_first(), and .split_last() for safely accessing the boundary elements of a vector in $O(1)$ time without manual index arithmetic (v[v.len() - 1]).

Summary of Boundary Access Methods

Method Receiver Return Type Empty Vector Behavior
v.first() &[T] Option<&T> None
v.first_mut() &mut [T] Option<&mut T> None
v.last() &[T] Option<&T> None
v.last_mut() &mut [T] Option<&mut T> None
v.split_first() &[T] Option<(&T, &[T])> None
v.split_first_mut() &mut [T] Option<(&mut T, &mut [T])> None
v.split_last() &[T] Option<(&T, &[T])> None
v.split_last_mut() &mut [T] Option<(&mut T, &mut [T])> None

Why .last() Beats v[v.len() - 1]

Writing v[v.len() - 1] on an empty vector underflows 0_usize - 1 and panics. Calling v.last() or v.last_mut() is equivalent to v.get(v.len().wrapping_sub(1)) and safely returns None when v is empty:

fn main() {
    let mut log = vec![String::from("boot"), String::from("ready")];
    let empty: Vec<i32> = Vec::new();

    println!("First: {:?}, Last: {:?}", log.first(), log.last());
    println!("Empty first: {:?}, Empty last: {:?}", empty.first(), empty.last());

    // Mutate the last element in place if present:
    if let Some(tail) = log.last_mut() {
        tail.push_str(" (ok)");
    }
    println!("Updated log: {log:?}");
}
First: Some("boot"), Last: Some("ready")
Empty first: None, Empty last: None
Updated log: ["boot", "ready (ok)"]

Separating Head and Tail with split_first() and split_first_mut()

When an algorithm needs to hold a mutable reference to the first element (&mut T) while simultaneously reading or mutating the rest of the vector (&mut [T]), v.split_first_mut() (and v.split_last_mut()) returns both non-overlapping borrows at once without triggering borrow-checker conflicts:

fn normalize_relative_to_first(values: &mut [i32]) {
    if let Some((first, rest)) = values.split_first_mut() {
        for item in rest.iter_mut() {
            *item -= *first;
        }
        *first = 0;
    }
}

fn main() {
    let mut readings = vec![50, 65, 80, 45];
    normalize_relative_to_first(&mut readings);
    println!("Normalized: {readings:?}");
}
Normalized: [0, 15, 30, -5]