๐Ÿฆ€ Functional Rust
๐Ÿ“– [View on hightechmind.io โ†’](https://hightechmind.io/rust/002-last-two) ---

Example 002: Last Two Elements

Difficulty: โญ Category: Lists & Pattern Matching OCaml Source: OCaml.org 99 Problems #2

Problem Statement

Find the last two elements of a list. Return them as a pair (tuple), or `None` if the list has fewer than two elements.

Learning Outcomes

OCaml Approach

OCaml uses nested pattern matching: `[] | [_] -> None`, `[x; y] -> Some (x, y)`, and `_ :: t -> last_two t`. The recursive structure naturally peels off the head until two elements remain.

Rust Approach

Three approaches: (1) direct length-based indexing for O(1) access, (2) recursive slice patterns mirroring OCaml, and (3) `windows(2).last()` for an iterator-based solution. All return references (`&T`) to avoid cloning.

Key Differences

1. Return type: OCaml returns `Some (x, y)` (owned copy); Rust returns `Option<(&T, &T)>` (borrowed references) 2. Tuple syntax: OCaml `(x, y)` vs Rust `(x, y)` โ€” similar, but Rust tuples can hold references 3. Slice access: Rust slices are contiguous memory โ†’ O(1) indexing; OCaml lists are linked โ†’ O(n) traversal 4. Windows iterator: Rust's `windows(n)` has no direct OCaml equivalent โ€” it exploits contiguous memory 5. Exhaustive matching: Both languages require exhaustive patterns, but Rust's slice patterns (`[_, rest @ ..]`) are less common than OCaml's list patterns
// Find the last two elements of a list

// Idiomatic Rust with slice patterns
fn last_two<T>(list: &[T]) -> Option<(&T, &T)> {
    match list {
        [.., a, b] => Some((a, b)),
        _ => None,
    }
}

// Alternative: using split_last
fn last_two_split<T>(list: &[T]) -> Option<(&T, &T)> {
    let (last, rest) = list.split_last()?;
    let (second_last, _) = rest.split_last()?;
    Some((second_last, last))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cases() {
        assert_eq!(last_two::<i32>(&[]), None);
        assert_eq!(last_two(&[1]), None);
        assert_eq!(last_two(&[1, 2]), Some((&1, &2)));
        assert_eq!(last_two(&[1, 2, 3, 4]), Some((&3, &4)));
    }
}

fn main() {
    println!("last_two([1,2,3,4]) = {:?}", last_two(&[1, 2, 3, 4]));
    println!("โœ“ Rust tests passed");
}
(* Find the last two elements of a list *)

let rec last_two = function
  | [] | [_] -> None
  | [x; y] -> Some (x, y)
  | _ :: t -> last_two t

(* Tests *)
let () =
  assert (last_two [] = None);
  assert (last_two [1] = None);
  assert (last_two [1; 2] = Some (1, 2));
  assert (last_two [1; 2; 3; 4] = Some (3, 4));
  print_endline "โœ“ OCaml tests passed"

๐Ÿ“Š Detailed Comparison

Comparison: Last Two Elements

OCaml

๐Ÿช Show OCaml equivalent
let rec last_two = function
| [] | [_] -> None
| [x; y] -> Some (x, y)
| _ :: t -> last_two t

Rust โ€” Idiomatic (direct indexing)

pub fn last_two<T>(slice: &[T]) -> Option<(&T, &T)> {
 let len = slice.len();
 if len < 2 { None }
 else { Some((&slice[len - 2], &slice[len - 1])) }
}

Rust โ€” Functional (recursive)

pub fn last_two_recursive<T>(slice: &[T]) -> Option<(&T, &T)> {
 match slice {
     [] | [_] => None,
     [x, y] => Some((x, y)),
     [_, rest @ ..] => last_two_recursive(rest),
 }
}

Rust โ€” Iterator (windows)

pub fn last_two_windows<T>(slice: &[T]) -> Option<(&T, &T)> {
 slice.windows(2).last().map(|w| (&w[0], &w[1]))
}

Comparison Table

AspectOCamlRust
Data structure`'a list` (linked list)`&[T]` (slice / contiguous)
Return type`('a * 'a) option``Option<(&T, &T)>`
OwnershipCopies values into tupleBorrows references
Pattern syntax`[x; y]``[x, y]`
ComplexityO(n) alwaysO(1) idiomatic, O(n) recursive

Type Signatures

  • OCaml: `val last_two : 'a list -> ('a * 'a) option`
  • Rust: `fn last_two<T>(slice: &[T]) -> Option<(&T, &T)>`

Takeaways

1. Rust's contiguous memory layout enables O(1) access to the last two elements โ€” OCaml's linked list requires full traversal 2. Rust returns borrowed references (`&T`) instead of copies, avoiding allocation for large types 3. Slice pattern matching in Rust (`[x, y]`, `[_, rest @ ..]`) closely mirrors OCaml's list patterns 4. The `windows()` iterator is uniquely Rust โ€” it exploits contiguous memory for sliding-window views 5. Both languages use `Option`/`option` to safely handle the "not enough elements" case without exceptions