🦀 Functional Rust
📖 [View on hightechmind.io →](https://hightechmind.io/rust/002-list-operations) ---

002-list-operations — List Operations

See [hightechmind.io/rust/002-list-operations](https://hightechmind.io/rust/002-list-operations) for the full explanation, OCaml comparison, and tests.

Quick Start

cargo test

Files

// 002: List Operations
// Core list operations: head, tail, length, append, reverse

// Approach 1: Vec methods
fn head(v: &[i32]) -> Option<&i32> {
    v.first()
}

fn tail(v: &[i32]) -> Option<&[i32]> {
    if v.is_empty() { None } else { Some(&v[1..]) }
}

fn length(v: &[i32]) -> usize {
    v.len()
}

fn append(a: &[i32], b: &[i32]) -> Vec<i32> {
    let mut result = a.to_vec();
    result.extend_from_slice(b);
    result
}

fn reverse(v: &[i32]) -> Vec<i32> {
    v.iter().rev().copied().collect()
}

// Approach 2: Recursive (functional style)
fn rec_length(v: &[i32]) -> usize {
    if v.is_empty() { 0 } else { 1 + rec_length(&v[1..]) }
}

fn rec_reverse(v: &[i32]) -> Vec<i32> {
    if v.is_empty() {
        vec![]
    } else {
        let mut rest = rec_reverse(&v[1..]);
        rest.push(v[0]);
        rest
    }
}

// Approach 3: Tail-recursive with accumulator
fn rev_acc(v: &[i32]) -> Vec<i32> {
    fn aux(slice: &[i32], acc: Vec<i32>) -> Vec<i32> {
        if slice.is_empty() {
            acc
        } else {
            let mut new_acc = vec![slice[0]];
            new_acc.extend(acc);
            aux(&slice[1..], new_acc)
        }
    }
    aux(v, vec![])
}

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    println!("head = {:?}", head(&v));
    println!("tail = {:?}", tail(&v));
    println!("length = {}", length(&v));
    println!("append = {:?}", append(&[1, 2], &[3, 4]));
    println!("reverse = {:?}", reverse(&v));
}

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

    #[test]
    fn test_head() {
        assert_eq!(head(&[1, 2, 3]), Some(&1));
        assert_eq!(head(&[]), None);
    }

    #[test]
    fn test_tail() {
        assert_eq!(tail(&[1, 2, 3]), Some([2, 3].as_slice()));
        assert_eq!(tail(&[]), None);
    }

    #[test]
    fn test_length() {
        assert_eq!(length(&[1, 2, 3]), 3);
        assert_eq!(length(&[]), 0);
    }

    #[test]
    fn test_append() {
        assert_eq!(append(&[1, 2], &[3, 4]), vec![1, 2, 3, 4]);
    }

    #[test]
    fn test_reverse() {
        assert_eq!(reverse(&[1, 2, 3]), vec![3, 2, 1]);
    }

    #[test]
    fn test_rec_length() {
        assert_eq!(rec_length(&[1, 2, 3, 4]), 4);
    }

    #[test]
    fn test_rec_reverse() {
        assert_eq!(rec_reverse(&[1, 2, 3]), vec![3, 2, 1]);
    }

    #[test]
    fn test_rev_acc() {
        assert_eq!(rev_acc(&[1, 2, 3]), vec![3, 2, 1]);
    }
}
(* 002: List Operations *)
(* Core list operations: head, tail, length, append, reverse *)

(* Approach 1: Standard library *)
let head_of lst = List.hd lst
let tail_of lst = List.tl lst
let length_of lst = List.length lst
let append_lists a b = a @ b
let reverse_list lst = List.rev lst

(* Approach 2: Pattern matching — safer *)
let safe_head = function
  | [] -> None
  | x :: _ -> Some x

let safe_tail = function
  | [] -> None
  | _ :: xs -> Some xs

let rec my_length = function
  | [] -> 0
  | _ :: xs -> 1 + my_length xs

let rec my_append a b =
  match a with
  | [] -> b
  | x :: xs -> x :: my_append xs b

let rec my_reverse = function
  | [] -> []
  | x :: xs -> my_reverse xs @ [x]

(* Approach 3: Tail-recursive reverse *)
let rev_tr lst =
  let rec aux acc = function
    | [] -> acc
    | x :: xs -> aux (x :: acc) xs
  in
  aux [] lst

(* Tests *)
let () =
  assert (head_of [1; 2; 3] = 1);
  assert (tail_of [1; 2; 3] = [2; 3]);
  assert (length_of [1; 2; 3] = 3);
  assert (append_lists [1; 2] [3; 4] = [1; 2; 3; 4]);
  assert (reverse_list [1; 2; 3] = [3; 2; 1]);
  assert (safe_head [] = None);
  assert (safe_head [42] = Some 42);
  assert (safe_tail [] = None);
  assert (my_length [1; 2; 3; 4] = 4);
  assert (my_append [1] [2; 3] = [1; 2; 3]);
  assert (my_reverse [1; 2; 3] = [3; 2; 1]);
  assert (rev_tr [1; 2; 3] = [3; 2; 1]);
  Printf.printf "✓ All tests passed\n"

📊 Detailed Comparison

Core Insight

OCaml's list is an immutable singly-linked list (O(1) prepend, O(n) append). Rust's `Vec<T>` is a contiguous growable array (O(1) push to end, O(n) insert at front). This fundamental difference shapes idiomatic usage.

OCaml Approach

  • `List.hd` / `List.tl` for head/tail (raise exception on empty)
  • `List.length` counts nodes — O(n)
  • `@` or `List.append` for concatenation — O(n) in first list
  • `List.rev` for reverse — O(n)
  • Pattern matching preferred over `hd`/`tl`

Rust Approach

  • `.first()` / `.last()` return `Option<&T>`
  • `.len()` is O(1) — stored metadata
  • `.extend()` or `[a, b].concat()` for append
  • `.reverse()` in-place or `.iter().rev().collect()`
  • Slices `&[T]` for borrowing sub-ranges

Comparison Table

OperationOCamlRust Vec
Head`List.hd` / pattern match`.first()` → `Option`
Tail`List.tl``&v[1..]` slice
Length`List.length` O(n)`.len()` O(1)
Prepend`x :: lst` O(1)`.insert(0, x)` O(n)
Append`lst @ [x]` O(n)`.push(x)` O(1) amortized
Reverse`List.rev``.reverse()` in-place