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

307: Error Propagation in Closures

Difficulty: 3 Level: Advanced `?` works in closures โ€” but only when the closure itself returns `Result`, and that changes how you write iterators.

The Problem This Solves

The `?` operator is Rust's answer to early return on error โ€” concise, ergonomic, readable. But `?` only works inside functions (or closures) whose return type is `Result` or `Option`. When you pass a closure to `.map()`, the closure's return type is whatever the outer iterator chain expects โ€” and that's usually just `T`, not `Result<T, E>`. This creates a genuine tension: iterator adapters like `.map()` are your most powerful compositional tool, but they fight the error-handling model you rely on everywhere else. The common mistake is trying to use `?` in a `.map()` closure and hitting a confusing type error. The fix isn't to abandon iterators โ€” it's to learn the idioms that restore both ergonomics and correctness. This tension is fundamental: Rust's iterators assume success by default, but the real world produces failures. Several patterns exist, each with different trade-offs between early exit, error collection, and silent skipping.

The Intuition

The key insight: if you want `?` inside a closure, make the closure return `Result`. Then `.collect::<Result<Vec<_>, _>>()` does the rest โ€” it short-circuits on the first error and either gives you a clean `Vec` or the first failure. If you want to skip failures silently, use `.filter_map(|r| r.ok())`. If you want to accumulate until failure, use `.try_fold()`.

How It Works in Rust

// Pattern 1: collect into Result โ€” fail-fast
let numbers: Result<Vec<i32>, _> = ["1", "2", "bad"]
 .iter()
 .map(|s| s.parse::<i32>())   // each closure returns Result<i32, _>
 .collect();                   // short-circuits on first Err

// Pattern 2: filter_map โ€” silently drop failures
let valid: Vec<i32> = ["1", "bad", "3"]
 .iter()
 .filter_map(|s| s.parse::<i32>().ok())
 .collect();  // โ†’ [1, 3]

// Pattern 3: closure uses ? explicitly (return type must be Result)
let doubled: Vec<Result<i32, _>> = inputs.iter()
 .map(|s| -> Result<i32, _> { Ok(s.parse::<i32>()? * 2) })
 .collect();

// Pattern 4: try_fold for stateful accumulation
let sum = inputs.iter().try_fold(0i32, |acc, s| {
 Ok(acc + s.parse::<i32>()?)
});

What This Unlocks

Key Differences

ConceptOCamlRust
Error in map`List.filter_map` or `List.map` with `Result`Multiple patterns
Fail-fast collectionManual `fold` with early return`.collect::<Result<Vec<_>,_>>()`
Skip errors`List.filter_map``.filter_map(\r\r.ok())`
Early return in closureNatural with `let*` / `Result.bind`Closure must return `Result` for `?`
Stateful short-circuitRecursive or `fold` with option`.try_fold()`
//! 307. Error propagation in closures
//!
//! `?` in closures requires the closure to return `Result`/`Option`.

fn parse_number(s: &str) -> Result<i32, String> {
    s.trim().parse::<i32>().map_err(|_| format!("not a number: '{}'", s))
}

fn main() {
    // Pattern 1: collect into Result<Vec> (short-circuits on first error)
    let strs = ["1", "2", "3", "4"];
    let numbers: Result<Vec<i32>, String> = strs.iter()
        .map(|s| parse_number(s))
        .collect();
    println!("Pattern 1 (collect): {:?}", numbers);

    // Pattern 2: filter_map for Option (silently drops failures)
    let mixed = ["1", "bad", "3", "also_bad", "5"];
    let valid: Vec<i32> = mixed.iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();
    println!("Pattern 2 (filter_map): {:?}", valid);

    // Pattern 3: closure returning Result (can use ?)
    let results: Vec<Result<i32, _>> = mixed.iter()
        .map(|s| -> Result<i32, _> {
            let n = s.trim().parse::<i32>()?;
            if n < 0 { return Err("negative".into()); }
            Ok(n * 2)
        })
        .collect();
    println!("Pattern 3 (collect results): {:?}", results);

    // Pattern 4: try_fold for short-circuit accumulation
    let sum = strs.iter().try_fold(0i32, |acc, s| -> Result<i32, String> {
        Ok(acc + parse_number(s)?)
    });
    println!("Pattern 4 (try_fold sum): {:?}", sum);

    // Pattern 5: extract to named function to use ?
    fn process_all(inputs: &[&str]) -> Result<Vec<i32>, String> {
        inputs.iter().map(|s| parse_number(s)).collect()
    }
    println!("Pattern 5 (named fn): {:?}", process_all(&["10", "20", "30"]));
    println!("Pattern 5 (with err): {:?}", process_all(&["10", "bad"]));
}

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

    #[test]
    fn test_collect_all_ok() {
        let result: Result<Vec<i32>, _> = ["1","2","3"].iter()
            .map(|s| parse_number(s)).collect();
        assert_eq!(result.unwrap(), vec![1,2,3]);
    }

    #[test]
    fn test_filter_map_drops_errors() {
        let result: Vec<i32> = ["1","bad","3"].iter()
            .filter_map(|s| s.parse::<i32>().ok()).collect();
        assert_eq!(result, vec![1,3]);
    }

    #[test]
    fn test_try_fold() {
        let sum = ["1","2","3"].iter()
            .try_fold(0i32, |acc, s| -> Result<i32, String> {
                Ok(acc + parse_number(s)?)
            });
        assert_eq!(sum, Ok(6));
    }
}
(* 307. Error propagation in closures - OCaml *)

let () =
  let parse_all strs =
    let results = List.map (fun s ->
      match int_of_string_opt s with
      | Some n -> Ok n
      | None -> Error ("not a number: " ^ s)
    ) strs in
    List.fold_right (fun r acc ->
      match r, acc with
      | Ok v, Ok vs -> Ok (v :: vs)
      | Error e, _ -> Error e
      | _, Error e -> Error e
    ) results (Ok [])
  in
  let good = ["1"; "2"; "3"; "4"] in
  let bad = ["1"; "two"; "3"] in
  Printf.printf "Good: %s\n"
    (match parse_all good with Ok vs -> "[" ^ String.concat "," (List.map string_of_int vs) ^ "]" | Error e -> e);
  Printf.printf "Bad: %s\n"
    (match parse_all bad with Ok _ -> "Ok" | Error e -> "Error: " ^ e)