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

292: Option Combinators

Difficulty: 1 Level: Beginner Work with optional values using `.map()`, `.filter()`, `.and_then()`, and `.unwrap_or()` โ€” without checking for `None` every single time.

The Problem This Solves

Every programmer knows the pain of null checks. In Python you write `if user is not None:`, in Java you get `NullPointerException` at runtime, in JavaScript `undefined` quietly propagates through your code for three function calls before you notice. Rust's `Option<T>` is its solution: a value is either `Some(x)` (it exists) or `None` (it doesn't). The compiler forces you to handle both cases. But if you use `match` every time you want to touch an optional value, your code becomes verbose:
match user.get("alice") {
 Some(age) => match age.checked_mul(*age) {
     Some(squared) => println!("{}", squared),
     None => println!("overflow"),
 },
 None => println!("user not found"),
}
Option combinators flatten this into a single readable chain. The `None` case propagates automatically โ€” you only describe the happy path.

The Intuition

If you've used JavaScript's optional chaining (`user?.age`) or Python's walrus operator, you're already thinking the right way. Option combinators just make it explicit and type-safe. Think of `Option` like a box that might be empty: `.and_then()` is the key one: it's for when the next step also might return nothing (like looking up a config value that might not exist, then parsing it as a number that might not be valid).

How It Works in Rust

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

let some5: Option<i32> = Some(5);
let none: Option<i32> = None;

// .map(): transform Some, leave None alone
some5.map(|x| x * 2)  // Some(10)
none.map(|x| x * 2)   // None โ€” the closure never runs

// .filter(): keep Some only if the value passes the test
Some(5i32).filter(|&x| x % 2 == 0)  // None  โ€” 5 is odd
Some(6i32).filter(|&x| x % 2 == 0)  // Some(6) โ€” 6 is even

// .and_then(): chain optional computations
// Each step can return None, short-circuiting the rest
let result = Some("4.0")
 .and_then(|s| s.parse::<f64>().ok())  // parse string โ†’ None if invalid
 .and_then(safe_sqrt);                  // sqrt โ†’ None if negative
// Some(2.0)

// None short-circuits the whole chain
let result2: Option<f64> = None
 .and_then(|s: &str| s.parse::<f64>().ok())
 .and_then(safe_sqrt);
// None โ€” nothing runs after the first None

// .or() and .or_else(): provide fallbacks
none.or(Some(42))              // Some(42)
none.or_else(|| Some(99))      // Some(99) โ€” or_else is lazy (closure runs only if needed)

// Real example: HashMap lookup โ†’ transform
use std::collections::HashMap;
let users = HashMap::from([("alice", 30u32)]);
let age_squared = users.get("alice").map(|&age| age * age);
// Some(900)
let missing    = users.get("bob").map(|&age| age * age);
// None
The `&` inside `.filter(|&x| ...)` pattern-matches the reference โ€” it's pulling the value out of the `&i32`. You'll see this often with Option and iterator closures.

What This Unlocks

Key Differences

ConceptOCamlRust
Map Some value`Option.map f opt``opt.map(f)`
Chain optional ops`Option.bind opt f``opt.and_then(f)`
Filter by predicateManual `match``opt.filter(pred)`
Default value`Option.value ~default opt``opt.unwrap_or(default)`
Lazy fallback`Option.value_or_thunk``opt.unwrap_or_else(f)`
//! 292. map(), filter(), and_then() on Option
//!
//! Option combinators replace verbose match expressions with composable chains.

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

fn main() {
    let some5: Option<i32> = Some(5);
    let none: Option<i32> = None;

    // map: transform Some, pass through None
    println!("map Some(5)*2: {:?}", some5.map(|x| x * 2));
    println!("map None*2:    {:?}", none.map(|x| x * 2));

    // filter: keep Some only if predicate holds
    println!("filter even Some(5): {:?}", some5.filter(|&x| x % 2 == 0));
    println!("filter even Some(6): {:?}", Some(6i32).filter(|&x| x % 2 == 0));

    // and_then: chain optional computations (monadic bind)
    let config_str: Option<&str> = Some("4.0");
    let result = config_str
        .and_then(|s| s.parse::<f64>().ok())
        .and_then(safe_sqrt);
    println!("Parse and sqrt '4.0': {:?}", result);

    // Short-circuit on None
    let bad: Option<&str> = None;
    let result2 = bad.and_then(|s| s.parse::<f64>().ok()).and_then(safe_sqrt);
    println!("None chain: {:?}", result2);

    // or and or_else: provide defaults
    let default = none.or(Some(42));
    println!("None.or(Some(42)): {:?}", default);

    let computed = none.or_else(|| {
        println!("  (computing default...)");
        Some(99)
    });
    println!("or_else: {:?}", computed);

    // Chain of operations on a user lookup
    let users = std::collections::HashMap::from([
        ("alice", 30u32), ("bob", 25),
    ]);
    let age_squared = users.get("alice")
        .map(|&age| age * age);
    println!("alice's ageยฒ: {:?}", age_squared);
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_map_some() {
        assert_eq!(Some(5i32).map(|x| x * 2), Some(10));
    }

    #[test]
    fn test_map_none() {
        assert_eq!(None::<i32>.map(|x| x * 2), None);
    }

    #[test]
    fn test_filter() {
        assert_eq!(Some(4i32).filter(|&x| x % 2 == 0), Some(4));
        assert_eq!(Some(3i32).filter(|&x| x % 2 == 0), None);
    }

    #[test]
    fn test_and_then_chain() {
        let result = Some("4.0")
            .and_then(|s| s.parse::<f64>().ok())
            .map(|x| x * x);
        assert!((result.unwrap() - 16.0).abs() < 1e-10);
    }

    #[test]
    fn test_or_default() {
        let result = None::<i32>.or(Some(42));
        assert_eq!(result, Some(42));
    }
}
(* 292. map(), filter(), and_then() on Option - OCaml *)

let () =
  let some_5 = Some 5 in
  let none : int option = None in

  (* map: transforms Some value, passes through None *)
  Printf.printf "map Some(5)*2: %s\n"
    (match Option.map (fun x -> x * 2) some_5 with Some n -> string_of_int n | None -> "None");
  Printf.printf "map None*2: %s\n"
    (match Option.map (fun x -> x * 2) none with Some n -> string_of_int n | None -> "None");

  (* bind/and_then: chain optional computations *)
  let safe_div x y = if y = 0 then None else Some (x / y) in
  let result = Option.bind some_5 (fun n -> safe_div 10 n) in
  Printf.printf "10/5 chained: %s\n"
    (match result with Some n -> string_of_int n | None -> "None");

  (* filter *)
  let even = Option.filter (fun x -> x mod 2 = 0) some_5 in
  Printf.printf "filter even Some(5): %s\n"
    (match even with Some n -> string_of_int n | None -> "None");
  let even6 = Option.filter (fun x -> x mod 2 = 0) (Some 6) in
  Printf.printf "filter even Some(6): %s\n"
    (match even6 with Some n -> string_of_int n | None -> "None")