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

581: Result Matching Idioms

Difficulty: 2 Level: Beginner Handle success and failure explicitly with `Result<T, E>` โ€” propagate errors with `?`, match when you need both paths.

The Problem This Solves

Exception-based error handling is invisible. A function throws โ€” or doesn't โ€” and the caller has no way to tell from the signature. You either wrap everything in try/catch (verbose, often forgotten) or let exceptions bubble (dangerous). Forgetting to handle an error is always valid code. Go's two-return-value convention (`value, err := f()`) makes errors visible but doesn't force you to check them. You can write `value, _ := f()` and silently discard the error. Rust's `Result<T, E>` is the "you must deal with this" type. A function that can fail returns `Result`. Ignoring it triggers a compiler warning. Accessing the value without checking requires an explicit `unwrap()` โ€” a deliberate choice, not an oversight.

The Intuition

`Result<T, E>` has two variants: `Ok(T)` (success, carries the value) and `Err(E)` (failure, carries the error). Like `Option`, you match on it or use combinators. Unlike `Option`, the error carries information. The `?` operator is the ergonomic key: it means "if this is `Err`, return the `Err` from my function immediately; if it's `Ok`, give me the inner value." It's shorthand for the match-and-early-return pattern, but it composes. Chain five operations that might fail โ€” each gets a `?` โ€” and the error from whichever one fails propagates automatically. OCaml spells `?` as `let*` with `Result.bind`. The logic is identical; the syntax differs. The real power is when `collect()` works on `Vec<Result<T, E>>`: it either gives you `Ok(Vec<T>)` (all successes) or `Err(E)` (first failure). One call, no loop.

How It Works in Rust

#[derive(Debug)]
enum AppErr { Parse(ParseIntError), Range(i32), DivZero }

fn parse(s: &str) -> Result<i32, AppErr> {
 s.parse().map_err(AppErr::Parse)  // convert ParseIntError to AppErr
}

fn validate(n: i32) -> Result<i32, AppErr> {
 if n >= 1 && n <= 100 { Ok(n) } else { Err(AppErr::Range(n)) }
}

// ? chains โ€” each step propagates errors automatically
fn process(s: &str) -> Result<i32, AppErr> {
 let n = parse(s)?;    // Err(ParseError) if not a number
 let v = validate(n)?; // Err(Range) if out of bounds
 Ok(v * v)             // success
}

// match on Result โ€” explicit handling
match process("42") {
 Ok(v)  => println!("got {}", v),
 Err(e) => println!("failed: {:?}", e),
}

// Combinators โ€” like Option
let r: Result<i32, _> = Ok(5);
r.map(|x| x * 2);  // Ok(10)
r.map_err(|e| format!("error: {:?}", e));  // transforms the error

// collect() on Vec<Result<T, E>> โ€” all-or-nothing
let inputs = vec!["1", "2", "3"];
let nums: Result<Vec<i32>, _> = inputs.iter().map(|s| parse(s)).collect();
// Ok([1, 2, 3]) โ€” all succeed

let mixed = vec!["1", "x", "3"];
let result: Result<Vec<i32>, _> = mixed.iter().map(|s| parse(s)).collect();
// Err(Parse(...)) โ€” first failure wins

What This Unlocks

Key Differences

ConceptOCamlRust
Type`('a, 'e) result``Result<T, E>`
Variants`Ok x`, `Error e``Ok(x)`, `Err(e)`
Chain/bind`Result.bind r f` or `let``r.and_then(f)` or `?`
Early return on errorVia `let` monad`?` operator
Map error`Result.map_error f r``r.map_err(f)`
CollectManual fold`collect::<Result<Vec<_>, _>>()`
use std::num::ParseIntError;

#[derive(Debug)]
enum Err { Parse(ParseIntError), Range(i32), DivZero }
impl std::fmt::Display for Err {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Err::Parse(e)  => write!(f, "parse: {}", e),
            Err::Range(n)  => write!(f, "{} out of range", n),
            Err::DivZero   => write!(f, "div by zero"),
        }
    }
}

fn parse(s: &str) -> Result<i32, Err> { s.parse().map_err(Err::Parse) }
fn validate(n: i32) -> Result<i32, Err> {
    if n>=1 && n<=100 { Ok(n) } else { Err(Err::Range(n)) }
}

// ? operator chains
fn process(s: &str) -> Result<i32, Err> {
    let n = parse(s)?;
    let v = validate(n)?;
    Ok(v * v)
}

fn main() {
    for s in ["42","abc","0","100","101"] {
        match process(s) {
            Ok(v)    => println!("{} -> {}", s, v),
            Err(e) => println!("{} -> Err: {}", s, e),
        }
    }
    // combinators
    let r: Result<i32,Err> = Ok(5);
    println!("map: {:?}", r.map(|x|x*2).ok());

    // collect: Vec<Result> -> Result<Vec>
    let ok_strs = vec!["1","2","3"];
    let ok_nums: Result<Vec<i32>,_> = ok_strs.iter().map(|s|parse(s)).collect();
    println!("collect ok: {:?}", ok_nums.ok());

    let bad_strs = vec!["1","x","3"];
    let bad: Result<Vec<i32>,_> = bad_strs.iter().map(|s|parse(s)).collect();
    println!("collect bad err: {}", bad.is_err());
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn ok()   { assert!(process("42").is_ok()); }
    #[test] fn abc()  { assert!(process("abc").is_err()); }
    #[test] fn zero() { assert!(process("0").is_err()); }
}
(* Result chaining in OCaml *)
let (let*) = Result.bind

let parse s = match int_of_string_opt s with
  | Some n -> Ok n | None -> Error (Printf.sprintf "not int: %s" s)

let validate n =
  if n>=1 && n<=100 then Ok n else Error (Printf.sprintf "%d out of range" n)

let process s =
  let* n = parse s in
  let* v = validate n in
  Ok (v*v)

let () =
  List.iter (fun s ->
    match process s with
    | Ok v  -> Printf.printf "%s->%d\n" s v
    | Error e -> Printf.printf "%s->Err:%s\n" s e
  ) ["42";"abc";"0";"100";"101"]