ExamplesBy LevelBy TopicLearning Paths
278 Intermediate

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

  • • Understand that last() must consume the entire iterator to find the final element
  • • Use last() after filter() to find the last matching element
  • • Recognize that last() on a DoubleEndedIterator (like slice iterators) can optimize to O(1)
  • • Distinguish slice last() from iterator last() — same semantics, different implementations
  • Code 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

  • O(n) cost: Both Rust and OCaml must consume the full iterator/list to find the last element of a forward-only structure.
  • DoubleEnded optimization: Rust's last() on DoubleEndedIterator can call next_back() for O(1) access; OCaml has no equivalent built-in optimization.
  • Memory: Rust's last() keeps only one element in memory at a time; OCaml's List.rev allocates a full reversed list.
  • Composability: 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

  • Find the last line in a log file (simulated as a Vec<String>) that matches a warning pattern.
  • Implement a function that returns the last N elements of an iterator without materializing the full iterator — use a VecDeque as a sliding window.
  • Verify that last() and fold(None, |_, x| Some(x)) produce identical results.
  • Open Source Repos