🦀 Functional Rust
📖 [View on hightechmind.io →](https://hightechmind.io/rust/001-last-element) ---

001 — Last Element of a List

Difficulty: ⭐ Category: Lists & Pattern Matching Source: [OCaml.org 99 Problems #1](https://ocaml.org/problems#1) ---

Problem Statement

Find the last element of a list. Return `None` if the list is empty.
last([1, 2, 3, 4])  →  Some(4)
last([])             →  None
---

Learning Outcomes

---

OCaml Approach

OCaml uses linked lists and recursive pattern matching as a first-class idiom:
let rec last = function
| []  -> None
| [x] -> Some x
| _ :: t -> last t
The compiler optimises tail calls, so deep recursion is safe. The standard library's `List.rev` + head-match is also common.

Rust Approach

Rust represents sequences as slices (`&[T]`), which are contiguous in memory. This enables O(1) random access, so `slice::last()` is the natural first choice. Recursive slice-pattern matching is supported but not tail-call optimised, making it educational rather than production-grade.
// O(1) — preferred
pub fn last<T>(list: &[T]) -> Option<&T> {
 list.last()
}
---

Key Differences

AspectOCamlRust
Primary sequence typeLinked listSlice / Vec
Recursive styleIdiomatic, TCO guaranteedSupported, no TCO
Null safety`option` type`Option<T>`
Memory modelGC-managedBorrow checker enforces lifetimes
Stdlib call`List.rev lst \List.hd_opt``slice.last()`
// Find the last element of a list

// Solution 1: Idiomatic Rust (O(1) slice access)
fn last<T>(list: &[T]) -> Option<&T> {
    list.last()
}

// Solution 2: Pattern matching (functional style)
fn last_pattern<T>(list: &[T]) -> Option<&T> {
    match list {
        [] => None,
        [.., last] => Some(last),
    }
}

// Solution 3: Recursive (like OCaml, but not idiomatic)
fn last_recursive<T>(list: &[T]) -> Option<&T> {
    match list {
        [] => None,
        [x] => Some(x),
        [_, rest @ ..] => last_recursive(rest),
    }
}

// Solution 4: Iterator-based (also idiomatic)
fn last_iter<T>(list: &[T]) -> Option<&T> {
    list.iter().last()
}

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

    #[test]
    fn test_empty() {
        let empty: Vec<i32> = vec![];
        assert_eq!(last(&empty), None);
    }

    #[test]
    fn test_single() {
        assert_eq!(last(&[1]), Some(&1));
    }

    #[test]
    fn test_multiple() {
        assert_eq!(last(&[1, 2, 3, 4]), Some(&4));
        assert_eq!(last(&["a", "b", "c", "d"]), Some(&"d"));
    }

    #[test]
    fn test_all_implementations() {
        let list = vec![1, 2, 3, 4];
        assert_eq!(last(&list), last_pattern(&list));
        assert_eq!(last(&list), last_recursive(&list));
        assert_eq!(last(&list), last_iter(&list));
    }
}

fn main() {
    println!("last([1,2,3,4]) = {:?}", last(&[1, 2, 3, 4]));
    println!("last([]) = {:?}", last::<i32>(&[]));
    println!("✓ All tests passed");
}

/* Output:
   last([1,2,3,4]) = Some(4)
   last([]) = None
   ✓ All tests passed
*/
(* Find the last element of a list *)

(* Solution 1: Pattern matching (idiomatic OCaml) *)
let rec last = function
  | [] -> None
  | [x] -> Some x
  | _ :: t -> last t

(* Solution 2: Using standard library *)
let last_stdlib lst =
  match List.rev lst with
  | [] -> None
  | h :: _ -> Some h

(* Solution 3: Tail-recursive (better for long lists) *)
let last_tail lst =
  let rec aux acc = function
    | [] -> acc
    | h :: t -> aux (Some h) t
  in
  aux None lst

(* Tests *)
let () =
  assert (last [] = None);
  assert (last [1] = Some 1);
  assert (last [1; 2; 3; 4] = Some 4);
  assert (last ["a"; "b"; "c"; "d"] = Some "d");
  
  print_endline "✓ All tests passed"

(* Output:
   ✓ All tests passed
*)

📊 Detailed Comparison

OCaml vs Rust: Last Element of a List

Side-by-Side Code

OCaml — idiomatic recursive

🐪 Show OCaml equivalent
let rec last = function
| []  -> None
| [x] -> Some x
| _ :: t -> last t

OCaml — stdlib (`List.rev`)

🐪 Show OCaml equivalent
let last_stdlib lst =
match List.rev lst with
| []    -> None
| h :: _ -> Some h

OCaml — tail-recursive

🐪 Show OCaml equivalent
let last_tail lst =
let rec aux acc = function
 | []     -> acc
 | h :: t -> aux (Some h) t
in
aux None lst

---

Rust — idiomatic (`slice::last`)

pub fn last<T>(list: &[T]) -> Option<&T> {
 list.last()   // O(1): reads the final index directly
}

Rust — stdlib via iterator

pub fn last_stdlib<T>(list: &[T]) -> Option<&T> {
 list.iter().last()   // O(n): visits every element
}

Rust — recursive slice patterns

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

---

Comparison Table

AspectOCamlRust
Sequence type`'a list` (linked list, cons cells)`&[T]` (slice — contiguous memory)
Recursive styleIdiomatic; TCO guaranteed by the compilerSupported via slice patterns; no TCO
Stdlib one-liner`List.rev lst \List.hd_opt` — O(n)`slice.last()` — O(1)
Null safety`'a option``Option<T>`
Memory managementGCBorrow checker / lifetimes
Return typeOwned value (`'a option`)Borrowed reference (`Option<&T>`)
---

Type Signatures

🐪 Show OCaml equivalent
(* OCaml — polymorphic, returns owned value *)
val last : 'a list -> 'a option

// Rust — generic, returns borrowed reference
fn last<T>(list: &[T]) -> Option<&T>
//          ^^^^  borrows     ^ borrows back from input

The key difference: OCaml's `Some x` copies (or shares via GC) the value. Rust's `Some(&x)` is a reference into the original slice — the caller must keep `list` alive for as long as the returned `Option<&T>` is used.

---

Slice Patterns vs Cons Patterns

FeatureOCamlRust
Empty`[]``[]`
Single element`[x]``[x]`
Head + tail`h :: t``[h, rest @ ..]`
Last element direct(requires recursion)`[.., last]`
Rust slice patterns can match from either end, which OCaml cons patterns cannot — `[.., last]` directly binds the final element with no recursion.

---

5 Takeaways

1. `slice::last()` is O(1) in Rust; the recursive OCaml version is O(n). Slices know their length, so the stdlib call is a single pointer addition.

2. Rust has no guaranteed tail-call optimisation. The idiomatic answer to "tail-recursive traversal" in Rust is an iterator, not explicit recursion.

3. Rust returns a borrow, OCaml returns a value. `Option<&T>` ties the returned reference's lifetime to the slice. This is the borrow checker at work — memory safety without GC.

4. Slice patterns mirror cons patterns almost 1-to-1. Translating `_ :: t -> last t` to `[_, rest @ ..] => last_recursive(rest)` is mechanical, making OCaml → Rust migration of pattern-heavy code readable.

5. Prefer iteration over recursion in Rust. `list.iter().last()` and `list.last()` compile to tight loops or single instructions with no stack growth — the idiomatic preference in a language without TCO.