๐Ÿฆ€ 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

580: Option Matching Idioms

Difficulty: 2 Level: Beginner Work with `Option<T>` fluently โ€” combine direct matching with the combinator API.

The Problem This Solves

Null-safety is the original motivation. Every other language has a "billion-dollar mistake" โ€” the null reference โ€” that compiles fine and crashes at runtime. Rust has no null. Optional values are represented as `Option<T>`, and you cannot use the inner value without explicitly handling the `None` case. But that creates a new problem: verbose `match` blocks everywhere. `match opt { Some(v) => do_something(v), None => default }` is correct but noisy for simple transformations. And chaining operations on `Option` โ€” parse, validate, transform โ€” becomes a pyramid of `if let` blocks. The `Option` combinator API (`map`, `and_then`, `filter`, `unwrap_or`, `zip`) is the answer: transform an `Option<T>` without unpacking it. You write the happy-path logic; the `None` propagates automatically.

The Intuition

`Option<T>` is an enum with two variants: `Some(T)` (a value) and `None` (absent). Matching it exhaustively is the safe baseline. Combinators are the ergonomic layer on top. Think of `map` as "apply this function inside the box, if there is a box." `and_then` (OCaml: `bind`) is "apply this function that might fail โ€” returning another Option โ€” and flatten the result." `filter` discards a `Some` if the value doesn't pass a test. The combinator chain `opt.map(transform).and_then(validate).unwrap_or(default)` reads like a pipeline: transform if present, validate, fall back if anything fails. That's the Option monad. OCaml spells this with `let*` and custom bind operators. Rust puts it directly on the type. `filter_map` is the hidden gem of iterator processing: it maps and discards `None` in one pass, avoiding a separate `.filter()` + `.map()` chain.

How It Works in Rust

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

fn safe_sqrt(x: f64) -> Option<f64> {
 if x < 0.0 { None } else { Some(x.sqrt()) }
}

// Combinator chain โ€” None propagates through automatically
fn compute(a: i32, b: i32) -> Option<f64> {
 safe_div(a, b)
     .map(|q| q as f64)      // transform if Some
     .and_then(safe_sqrt)     // chain another fallible operation
     .map(|r| r * 2.0)       // transform the result
}

// filter_map โ€” transform + filter in one pass
let names: Vec<Option<&str>> = vec![Some("alice"), None, Some("bob")];
let upper: Vec<_> = names.iter()
 .filter_map(|o| o.map(str::to_uppercase))
 .collect();  // ["ALICE", "BOB"] โ€” None silently dropped

// Fallback values
let x: Option<i32> = None;
x.unwrap_or(0);              // 0 โ€” cheap default
x.unwrap_or_else(|| 42);     // 42 โ€” computed lazily
x.unwrap_or_default();       // 0 โ€” T's Default::default()

// and_then for chained fallible ops
let parsed = Some("42")
 .and_then(|s| s.parse::<i32>().ok())  // parse may fail
 .filter(|&n| n > 0);                   // discard negatives

// flatten, zip
Some(Some(42)).flatten();           // Some(42) โ€” remove one Option layer
Some(1).zip(Some("hello"));         // Some((1, "hello"))
Some(1).zip(None::<&str>);          // None

What This Unlocks

Key Differences

ConceptOCamlRust
Type`'a option``Option<T>`
Variants`Some x`, `None``Some(x)`, `None`
Map`Option.map f opt``opt.map(\x\f(x))`
Chain/bind`Option.bind opt f` or `let*``opt.and_then(f)`
Filter`Option.filter f opt``opt.filter(pred)`
Default`Option.value opt ~default``opt.unwrap_or(default)`
fn safe_div(a: i32, b: i32) -> Option<i32> { if b==0 {None} else {Some(a/b)} }
fn safe_sqrt(x: f64) -> Option<f64>         { if x<0.0{None} else {Some(x.sqrt())} }

fn compute(a: i32, b: i32) -> Option<f64> {
    safe_div(a, b).map(|q| q as f64).and_then(safe_sqrt).map(|r| r*2.0)
}

fn main() {
    for (a,b) in [(10,2),(10,0),(-4,2)] {
        match compute(a,b) {
            Some(v) => println!("{}/{} -> {:.2}", a, b, v),
            None    => println!("{}/{} -> None", a, b),
        }
    }

    let names: Vec<Option<&str>> = vec![Some("alice"),None,Some("bob")];
    let upper: Vec<_> = names.iter().filter_map(|o| o.map(str::to_uppercase)).collect();
    println!("{:?}", upper);

    // Combinators
    let x: Option<i32> = None;
    println!("unwrap_or: {}", x.unwrap_or(0));
    println!("unwrap_or_else: {}", x.unwrap_or_else(|| 42));
    println!("unwrap_or_default: {}", x.unwrap_or_default());

    let s: Option<&str> = Some("42");
    let parsed = s.and_then(|s| s.parse::<i32>().ok());
    println!("parsed: {:?}", parsed);
    println!("filtered: {:?}", parsed.filter(|&n| n > 0));

    // flatten, zip
    let nested: Option<Option<i32>> = Some(Some(42));
    println!("flatten: {:?}", nested.flatten());
    println!("zip: {:?}", Some(1).zip(Some("hello")));
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn div_ok()  { assert_eq!(safe_div(10,2), Some(5)); }
    #[test] fn div_zero(){ assert_eq!(safe_div(10,0), None); }
    #[test] fn combinator(){
        let opt: Option<i32> = Some(4);
        assert_eq!(opt.map(|x|x*2), Some(8));
        assert_eq!(opt.filter(|&x|x>10), None);
    }
}
(* Option idioms in OCaml *)
let (let*) = Option.bind

let safe_div a b = if b=0 then None else Some(a/b)
let safe_sqrt x = if x < 0.0 then None else Some(sqrt x)

let compute a b =
  let* q = safe_div a b in
  let* r = safe_sqrt (float_of_int q) in
  Some (r *. 2.0)

let () =
  let show label = function None->Printf.printf "%s: None\n" label
    | Some v -> Printf.printf "%s: %.2f\n" label v in
  show "10/2" (compute 10 2);
  show "10/0" (compute 10 0);
  show "-4/2" (compute (-4) 2);
  let names = [Some"alice";None;Some"bob"] in
  let upper = List.filter_map (Option.map String.capitalize_ascii) names in
  List.iter print_endline upper