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]