🦀 Functional Rust
🎬 Error Handling in Rust Option, Result, the ? operator, and combinators.
📝 Text version (for readers / accessibility)

• Option represents a value that may or may not exist — Some(value) or None

• Result represents success (Ok) or failure (Err) — no exceptions needed

• The ? operator propagates errors up the call stack concisely

• Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations

• The compiler forces you to handle every error case — no silent failures

Example 005: List Filter From Scratch

Difficulty: ⭐⭐ Category: Higher-Order Functions | Lists | Predicates OCaml Source: Functional programming classic; derives `List.filter`

Problem Statement

Implement a filter function that removes elements from a list that don't satisfy a given predicate (boolean test function). This is the fundamental operation for selecting a subset of elements while preserving order.

Learning Outcomes

OCaml Approach

OCaml's `filter` is recursive: it pattern-matches on the head and tail of the list, recursively filters the tail, and then conditionally prepends the head. This naturally preserves list order because the predicate is tested as each element is deconstructed.

Rust Approach

Rust provides three idiomatic paths: 1. Iterator chain (most idiomatic): `items.iter().filter(...).cloned().collect()` — leverages Rust's lazy iterators and standard library 2. Recursive pattern matching (closest to OCaml): matches on `[h, rest @ ..]` and recursively calls itself 3. Fold/accumulate (functional alternative): uses `fold` to build the result bottom-up All three preserve order and handle empty lists correctly. The iterator version is preferred in production Rust because it avoids allocations during traversal.

Key Differences

1. List representation: OCaml uses cons lists (`h :: t`); Rust uses slices (`&[T]`) 2. Recursion safety: OCaml's immutable recursion is safe by default; Rust requires `fn(&T)` to avoid mutable predicates 3. Cloning: Rust must `.clone()` elements when moving them into the result vector; OCaml lists share structure via references 4. Laziness: Rust iterators are lazy (elements processed on-demand); fold is eager (processes all elements immediately)
/// Filter removes elements that don't satisfy the predicate.
/// Idiomatic Rust: uses iterator chains like the Rust standard library
pub fn filter<T: Clone>(predicate: fn(&T) -> bool, items: &[T]) -> Vec<T> {
    items.iter().filter(|x| predicate(x)).cloned().collect()
}

/// Filter using immutable recursion — closer to OCaml style.
/// Shows the explicit pattern matching on the list structure.
pub fn filter_recursive<T: Clone>(predicate: fn(&T) -> bool, items: &[T]) -> Vec<T> {
    match items {
        [] => vec![],
        [h, rest @ ..] => {
            let mut tail = filter_recursive(predicate, rest);
            if predicate(h) {
                let mut result = vec![h.clone()];
                result.append(&mut tail);
                result
            } else {
                tail
            }
        }
    }
}

/// Filter using fold/reduce pattern — functional accumulation.
/// Demonstrates left fold over the slice.
pub fn filter_fold<T: Clone>(predicate: fn(&T) -> bool, items: &[T]) -> Vec<T> {
    items.iter().fold(vec![], |mut acc, x| {
        if predicate(x) {
            acc.push(x.clone());
        }
        acc
    })
}

// Predicates
pub fn is_even(x: &i32) -> bool {
    x % 2 == 0
}

pub fn is_odd(x: &i32) -> bool {
    x % 2 != 0
}

pub fn is_positive(x: &i32) -> bool {
    *x > 0
}

fn main() {
    let nums = vec![-2, -1, 0, 1, 2, 3, 4];

    print!("Evens: ");
    let evens = filter(is_even, &nums);
    for n in evens {
        print!("{} ", n);
    }
    println!();

    print!("Positives: ");
    let positives = filter(is_positive, &nums);
    for n in positives {
        print!("{} ", n);
    }
    println!();

    print!("Odds: ");
    let odds = filter(is_odd, &nums);
    for n in odds {
        print!("{} ", n);
    }
    println!();
}

/* Output:
   Evens: -2 0 2 4
   Positives: 1 2 3 4
   Odds: -1 1 3
*/
(* Filter removes elements that don't satisfy the predicate *)
let rec filter p = function
  | []     -> []
  | h :: t ->
    let t' = filter p t in
    if p h then h :: t' else t'

(* Specialised predicates *)
let is_even x = x mod 2 = 0
let is_odd x = x mod 2 <> 0
let is_positive x = x > 0

let () =
  let nums = [-2; -1; 0; 1; 2; 3; 4] in
  List.iter (Printf.printf "%d ") (filter is_even nums); print_newline ();
  List.iter (Printf.printf "%d ") (filter is_positive nums); print_newline ()

📊 Detailed Comparison

OCaml vs Rust: List Filter

Side-by-Side Code

OCaml

🐪 Show OCaml equivalent
let rec filter p = function
| []     -> []
| h :: t ->
 let t' = filter p t in
 if p h then h :: t' else t'

let is_even x = x mod 2 = 0

let () =
let nums = [-2; -1; 0; 1; 2; 3; 4] in
List.iter (Printf.printf "%d ") (filter is_even nums); print_newline ()

Rust (idiomatic)

pub fn filter<T: Clone>(predicate: fn(&T) -> bool, items: &[T]) -> Vec<T> {
 items.iter().filter(|x| predicate(x)).cloned().collect()
}

pub fn is_even(x: &i32) -> bool {
 x % 2 == 0
}

fn main() {
 let nums = vec![-2, -1, 0, 1, 2, 3, 4];
 let evens = filter(is_even, &nums);
 for n in evens {
     print!("{} ", n);
 }
 println!();
}

Rust (recursive)

pub fn filter_recursive<T: Clone>(predicate: fn(&T) -> bool, items: &[T]) -> Vec<T> {
 match items {
     [] => vec![],
     [h, rest @ ..] => {
         let mut tail = filter_recursive(predicate, rest);
         if predicate(h) {
             let mut result = vec![h.clone()];
             result.append(&mut tail);
             result
         } else {
             tail
         }
     }
 }
}

Type Signatures

ConceptOCamlRust
Filter function`('a -> bool) -> 'a list -> 'a list``fn(fn(&T) -> bool, &[T]) -> Vec<T>`
Predicate`'a -> bool``fn(&T) -> bool`
List type`'a list``Vec<T>` or `&[T]`
Empty list`[]``vec![]` or `[]` (pattern)
Cons`h :: t``[h, rest @ ..]` (pattern)

Key Insights

1. Pattern matching equivalence: OCaml's `h :: t` directly maps to Rust's `[h, rest @ ..]` slice pattern. Both deconstruct lists into head and tail in a single match expression.

2. Ownership vs. sharing: OCaml lists are immutable and share structure via references; `h :: t'` reuses memory. Rust vectors must `.clone()` each element because vectors own their data. This is why the idiomatic Rust version collects into `Vec<T>` instead of building a linked list.

3. Function types as parameters: OCaml's `'a -> bool` is a universal function type. Rust's `fn(&T) -> bool` is more restrictive—it requires a function pointer, not a closure. For general higher-order functions, Rust would use `impl Fn(&T) -> bool` or `Box<dyn Fn(&T) -> bool>`, but function pointers suffice here.

4. Idiomatic style divergence: OCaml recursion is the natural, encouraged pattern. Rust's idiomatic style favors iterators—they compose better, avoid stack depth issues, and integrate with the standard library. Recursion in Rust is a valid but secondary choice.

5. Predicate binding: In OCaml, the predicate `p` is bound once at the function's start. In Rust, we pass `predicate: fn(&T) -> bool` explicitly. This makes the dependency clearer and enables partial application via closures (though function pointers don't capture state).

When to Use Each Style

Use idiomatic Rust iterator when:

  • You're filtering within a data pipeline (map, filter, fold chains)
  • Performance matters (iterators may optimize better and avoid intermediate allocations)
  • The predicate is simple or from the standard library (e.g., `is_some`, `is_ok`)
Use recursive Rust when:
  • Teaching functional programming concepts or comparing directly with OCaml
  • The predicate has complex control flow that reads better as pattern matches
  • You need to preserve the functional recursion pattern for clarity
OCaml recursion is always natural because:
  • Immutable data structures encourage recursion
  • No stack depth concerns for typical list sizes
  • Pattern matching is the primary control flow mechanism