🦀 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

302: Option::transpose() — Collecting Optional Results

Difficulty: 2 Level: Intermediate Convert `Option<Result<T, E>>` into `Result<Option<T>, E>` — the key to clean iterator pipelines.

The Problem This Solves

You're iterating over optional values — perhaps from a config map where some keys exist and others don't. For the keys that exist, you need to parse their values. You get back `Option<Result<T, E>>` from each lookup — present-and-parseable, present-but-invalid, or absent. Now you want to propagate parse errors while filtering out absences. Without `transpose()`, you'd write a nested match at every step. With it, you can `filter_map` cleanly: transpose the `Option<Result<T,E>>` into `Result<Option<T>,E>`, and the transpose's semantics do the right thing — `None` becomes `Ok(None)` (which `filter_map` skips), `Some(Ok(v))` becomes `Ok(Some(v))` (which `filter_map` keeps), and `Some(Err(e))` becomes `Err(e)` (which propagates). The practical payoff: a one-liner that looks up optional config values, parses them, filters missing ones, and propagates any parse error — all without a single explicit `match`.

The Intuition

`Option::transpose()` moves the `Result` layer outside the `Option` — turning "maybe a result" into "either an error, or maybe a value."

How It Works in Rust

// Option::transpose() rules:
let some_ok: Option<Result<i32, &str>> = Some(Ok(42));
let some_err: Option<Result<i32, &str>> = Some(Err("bad"));
let none:    Option<Result<i32, &str>> = None;

some_ok.transpose()   // => Ok(Some(42))  — present and valid
some_err.transpose()  // => Err("bad")    — present but invalid: error propagates
none.transpose()      // => Ok(None)      — absent: treated as success with no value

// The killer use case: filter_map with error propagation
fn lookup_and_parse(
 map: &HashMap<&str, &str>,
 key: &str,
) -> Result<Option<i32>, ParseIntError> {
 map.get(key)              // Option<&&str>
    .map(|s| s.parse())   // Option<Result<i32, ParseIntError>>
    .transpose()          // Result<Option<i32>, ParseIntError>
}

// Collect a list: skip None, fail on bad parse, keep good values
let inputs: Vec<Option<&str>> = vec![Some("1"), None, Some("2"), Some("bad")];
let result: Result<Vec<i32>, _> = inputs.into_iter()
 .filter_map(|opt| opt.map(|s| s.parse::<i32>()).transpose())
 //  ↑ None → filtered out; Some(Err) → short-circuits; Some(Ok(v)) → kept
 .collect();
The `filter_map` + `transpose` idiom is the idiomatic way to "parse values that might not exist, fail on bad ones."

What This Unlocks

Key Differences

ConceptOCamlRust
`Some(Ok(v))` → `Ok(Some(v))`Manual match`Option::transpose()`
`Some(Err(e))` → `Err(e)`Manual match`Option::transpose()`
`None` → `Ok(None)`Manual match`Option::transpose()`
filter_map + error propagationManual fold`filter_map(oo.map(f).transpose()).collect()`
//! # Option::transpose() — Collecting Optional Results
//!
//! Convert `Option<Result<T, E>>` into `Result<Option<T>, E>`.

use std::collections::HashMap;

/// Lookup a key and parse its value
pub fn lookup_and_parse(
    map: &HashMap<&str, &str>,
    key: &str,
) -> Result<Option<i32>, std::num::ParseIntError> {
    map.get(key).map(|s| s.parse::<i32>()).transpose()
}

/// Filter and parse optional values
pub fn parse_optional_values(inputs: Vec<Option<&str>>) -> Result<Vec<i32>, std::num::ParseIntError> {
    inputs
        .into_iter()
        .filter_map(|opt| opt.map(|s| s.parse::<i32>()))
        .collect::<Result<Vec<_>, _>>()
}

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

    #[test]
    fn test_some_ok_transpose() {
        let v: Option<Result<i32, &str>> = Some(Ok(5));
        assert_eq!(v.transpose(), Ok(Some(5)));
    }

    #[test]
    fn test_some_err_transpose() {
        let v: Option<Result<i32, &str>> = Some(Err("fail"));
        assert_eq!(v.transpose(), Err("fail"));
    }

    #[test]
    fn test_none_transpose() {
        let v: Option<Result<i32, &str>> = None;
        assert_eq!(v.transpose(), Ok(None));
    }

    #[test]
    fn test_lookup_found() {
        let mut map = HashMap::new();
        map.insert("port", "8080");
        assert_eq!(lookup_and_parse(&map, "port").unwrap(), Some(8080));
    }

    #[test]
    fn test_lookup_missing() {
        let map: HashMap<&str, &str> = HashMap::new();
        assert_eq!(lookup_and_parse(&map, "port").unwrap(), None);
    }

    #[test]
    fn test_lookup_invalid() {
        let mut map = HashMap::new();
        map.insert("port", "bad");
        assert!(lookup_and_parse(&map, "port").is_err());
    }

    #[test]
    fn test_parse_optional_values() {
        let inputs = vec![Some("1"), None, Some("2")];
        let result = parse_optional_values(inputs);
        assert_eq!(result.unwrap(), vec![1, 2]);
    }
}
(* 302. Option/Result transpose patterns - OCaml *)

let transpose_option_result = function
  | Some (Ok v) -> Ok (Some v)
  | Some (Error e) -> Error e
  | None -> Ok None

let () =
  let test_cases = [
    Some (Ok 42);
    Some (Error "oops");
    None;
  ] in
  List.iter (fun r ->
    match transpose_option_result r with
    | Ok (Some n) -> Printf.printf "Ok(Some(%d))\n" n
    | Ok None -> print_endline "Ok(None)"
    | Error e -> Printf.printf "Error(%s)\n" e
  ) test_cases;

  (* Collecting optional results *)
  let inputs = [Some "1"; None; Some "3"; None; Some "abc"] in
  let results = List.map (Option.map int_of_string_opt) inputs in
  Printf.printf "First valid: %s\n"
    (match List.find_opt (fun x ->
       match x with Some (Some _) -> true | _ -> false) results with
     | Some (Some (Some n)) -> string_of_int n
     | _ -> "None")