🦀 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

📖 [View on hightechmind.io →](https://hightechmind.io/rust/004-option-result) ---

004-option-result — Option Result

See [hightechmind.io/rust/004-option-result](https://hightechmind.io/rust/004-option-result) for the full explanation, OCaml comparison, and tests.

Quick Start

cargo test

Files

// 004: Option and Result
// Safe handling of missing values and errors

// Approach 1: Option basics
fn safe_div(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}

fn safe_head(v: &[i32]) -> Option<i32> {
    v.first().copied()
}

fn find_even(v: &[i32]) -> Option<i32> {
    v.iter().find(|&&x| x % 2 == 0).copied()
}

// Approach 2: Chaining with map and and_then
fn double_head(v: &[i32]) -> Option<i32> {
    safe_head(v).map(|x| x * 2)
}

fn safe_div_then_add(a: i32, b: i32, c: i32) -> Option<i32> {
    safe_div(a, b).map(|q| q + c)
}

fn chain_lookups(v1: &[i32], v2: &[i32]) -> Option<i32> {
    safe_head(v1).and_then(|idx| v2.get(idx as usize).copied())
}

// Approach 3: Result for richer errors
#[derive(Debug, PartialEq)]
enum MyError {
    DivByZero,
    NegativeInput,
    EmptyList,
}

fn safe_div_r(a: i32, b: i32) -> Result<i32, MyError> {
    if b == 0 { Err(MyError::DivByZero) } else { Ok(a / b) }
}

fn safe_sqrt(x: f64) -> Result<f64, MyError> {
    if x < 0.0 { Err(MyError::NegativeInput) } else { Ok(x.sqrt()) }
}

fn safe_head_r(v: &[i32]) -> Result<i32, MyError> {
    v.first().copied().ok_or(MyError::EmptyList)
}

fn compute(v: &[i32]) -> Result<i32, MyError> {
    let x = safe_head_r(v)?;
    safe_div_r(x * 10, 3)
}

fn main() {
    println!("safe_div(10, 3) = {:?}", safe_div(10, 3));
    println!("safe_div(10, 0) = {:?}", safe_div(10, 0));
    println!("compute([5, 10]) = {:?}", compute(&[5, 10]));
    println!("compute([]) = {:?}", compute(&[]));
}

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

    #[test]
    fn test_safe_div() {
        assert_eq!(safe_div(10, 3), Some(3));
        assert_eq!(safe_div(10, 0), None);
    }

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

    #[test]
    fn test_find_even() {
        assert_eq!(find_even(&[1, 3, 4, 5]), Some(4));
        assert_eq!(find_even(&[1, 3, 5]), None);
    }

    #[test]
    fn test_double_head() {
        assert_eq!(double_head(&[5, 10]), Some(10));
        assert_eq!(double_head(&[]), None);
    }

    #[test]
    fn test_chain_lookups() {
        assert_eq!(chain_lookups(&[1], &[10, 20, 30]), Some(20));
        assert_eq!(chain_lookups(&[], &[10, 20]), None);
    }

    #[test]
    fn test_result() {
        assert_eq!(safe_div_r(10, 2), Ok(5));
        assert_eq!(safe_div_r(10, 0), Err(MyError::DivByZero));
    }

    #[test]
    fn test_compute() {
        assert_eq!(compute(&[5, 10]), Ok(16));
        assert_eq!(compute(&[]), Err(MyError::EmptyList));
    }
}
(* 004: Option and Result *)
(* Safe handling of missing values and errors *)

(* Approach 1: Option basics *)
let safe_div a b =
  if b = 0 then None else Some (a / b)

let safe_head = function
  | [] -> None
  | x :: _ -> Some x

let find_even lst =
  List.find_opt (fun x -> x mod 2 = 0) lst

(* Approach 2: Chaining with map and bind *)
let double_head lst =
  safe_head lst |> Option.map (fun x -> x * 2)

let safe_div_then_add a b c =
  safe_div a b |> Option.map (fun q -> q + c)

let chain_lookups lst1 lst2 =
  safe_head lst1
  |> Option.bind (fun idx ->
    if idx >= 0 && idx < List.length lst2
    then Some (List.nth lst2 idx)
    else None)

(* Approach 3: Result for richer errors *)
type error = DivByZero | NegativeInput | EmptyList

let safe_div_r a b =
  if b = 0 then Error DivByZero else Ok (a / b)

let safe_sqrt x =
  if x < 0.0 then Error NegativeInput
  else Ok (sqrt x)

let safe_head_r = function
  | [] -> Error EmptyList
  | x :: _ -> Ok x

let compute lst =
  match safe_head_r lst with
  | Error e -> Error e
  | Ok x -> safe_div_r (x * 10) 3

(* Tests *)
let () =
  assert (safe_div 10 3 = Some 3);
  assert (safe_div 10 0 = None);
  assert (safe_head [1; 2; 3] = Some 1);
  assert (safe_head [] = None);
  assert (double_head [5; 10] = Some 10);
  assert (double_head [] = None);
  assert (safe_div_then_add 10 3 5 = Some 8);
  assert (safe_div_then_add 10 0 5 = None);
  assert (chain_lookups [1] [10; 20; 30] = Some 20);
  assert (chain_lookups [] [10; 20] = None);
  assert (safe_div_r 10 2 = Ok 5);
  assert (safe_div_r 10 0 = Error DivByZero);
  assert (compute [5; 10] = Ok 16);
  assert (compute [] = Error EmptyList);
  Printf.printf "✓ All tests passed\n"

📊 Detailed Comparison

Core Insight

`Option` replaces null pointers. `Result` replaces exceptions. Both languages encode success/failure in the type system, forcing the caller to handle every case.

OCaml Approach

  • `option` type: `Some x | None`
  • `result` type: `Ok x | Error e`
  • `Option.map`, `Option.bind` for chaining
  • `Result.map`, `Result.bind` for chaining
  • Pattern matching is the primary way to unwrap

Rust Approach

  • `Option<T>`: `Some(x)` / `None`
  • `Result<T, E>`: `Ok(x)` / `Err(e)`
  • `.map()`, `.and_then()` for chaining
  • `?` operator for early return on error
  • `.unwrap_or()`, `.unwrap_or_else()` for defaults

Comparison Table

FeatureOCamlRust
Option type`'a option``Option<T>`
Result type`('a, 'e) result``Result<T, E>`
Map`Option.map f o``o.map(f)`
Bind/FlatMap`Option.bind o f``o.and_then(f)`
Default`Option.value ~default o``o.unwrap_or(d)`
Error propagationPattern match`?` operator