278: Getting the Last Element with last()
Functional Programming
Tutorial
The Problem
Retrieving the final element of a sequence is straightforward for slices (slice.last()) but requires consuming the entire iterator for non-indexed collections. The last() method provides a safe, idiomatic way to get the final element of any iterator, returning Option<T> to handle the empty case. Combined with filter(), it finds the last matching element in a single pass.
🎯 Learning Outcomes
last() must consume the entire iterator to find the final elementlast() after filter() to find the last matching elementlast() on a DoubleEndedIterator (like slice iterators) can optimize to O(1)last() from iterator last() — same semantics, different implementationsCode Example
#![allow(clippy::all)]
//! 278. Getting the last element
//!
//! `last()` consumes the iterator to return `Option<T>` with the final element.
#[cfg(test)]
mod tests {
#[test]
fn test_last_basic() {
let v = [1i32, 2, 3, 4, 5];
assert_eq!(v.iter().last(), Some(&5));
}
#[test]
fn test_last_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(empty.iter().last(), None);
}
#[test]
fn test_last_after_filter() {
let last_even = (1i32..=10).filter(|x| x % 2 == 0).last();
assert_eq!(last_even, Some(10));
}
#[test]
fn test_last_single() {
assert_eq!([42i32].iter().last(), Some(&42));
}
}Key Differences
last() on DoubleEndedIterator can call next_back() for O(1) access; OCaml has no equivalent built-in optimization.last() keeps only one element in memory at a time; OCaml's List.rev allocates a full reversed list.filter().last() is the clean Rust idiom; OCaml requires List.filter + last — two separate operations.OCaml Approach
OCaml's standard library provides List.rev then List.hd for the last element, though this allocates a reversed list. More efficient is a fold:
let last = function [] -> None | xs -> Some (List.nth xs (List.length xs - 1))
(* Or more efficiently: *)
let last lst = List.fold_left (fun _ x -> Some x) None lst
List.hd (List.rev lst) is common but allocates; the fold approach is O(n) without allocation.
Full Source
#![allow(clippy::all)]
//! 278. Getting the last element
//!
//! `last()` consumes the iterator to return `Option<T>` with the final element.
#[cfg(test)]
mod tests {
#[test]
fn test_last_basic() {
let v = [1i32, 2, 3, 4, 5];
assert_eq!(v.iter().last(), Some(&5));
}
#[test]
fn test_last_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(empty.iter().last(), None);
}
#[test]
fn test_last_after_filter() {
let last_even = (1i32..=10).filter(|x| x % 2 == 0).last();
assert_eq!(last_even, Some(10));
}
#[test]
fn test_last_single() {
assert_eq!([42i32].iter().last(), Some(&42));
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_last_basic() {
let v = [1i32, 2, 3, 4, 5];
assert_eq!(v.iter().last(), Some(&5));
}
#[test]
fn test_last_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(empty.iter().last(), None);
}
#[test]
fn test_last_after_filter() {
let last_even = (1i32..=10).filter(|x| x % 2 == 0).last();
assert_eq!(last_even, Some(10));
}
#[test]
fn test_last_single() {
assert_eq!([42i32].iter().last(), Some(&42));
}
}
Exercises
Vec<String>) that matches a warning pattern.VecDeque as a sliding window.last() and fold(None, |_, x| Some(x)) produce identical results.