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
- `example.ml` — OCaml implementation
- `example.rs` — Rust implementation
- `src/lib.rs` — Rust library code
- `COMPARISON.md` — Key differences between OCaml and Rust approaches
// 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
| Operation | OCaml | Rust 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 |