🦀 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

301: Result::transpose() — Flipping Nested Types

Difficulty: 2 Level: Intermediate Convert `Result<Option<T>, E>` into `Option<Result<T, E>>` — or back again.

The Problem This Solves

You're calling a function that returns `Result<Option<T>, E>` — perhaps a database lookup that might fail (IO error) or find nothing (the `None` case). Now you want to use it in a context that expects `Option<Result<T, E>>` — for example, to feed it into `Iterator::filter_map`. You end up writing a four-arm `match` to manually rearrange the two wrapper types. This awkward nesting comes up constantly when combining fallible operations with optional values. A config key might not exist (`None`) or parsing it might fail (`Err`). A cache lookup might miss (`None`) or deserializing the cached value might fail (`Err`). The two layers compose in predictable ways — and `transpose()` encodes those rules. Once you know the rules, the conversion is mechanical: `Ok(None)` means "success, no value" → `None`. `Ok(Some(v))` means "success, got value" → `Some(Ok(v))`. `Err(e)` means "failure" → `Some(Err(e))`. `transpose()` just applies these rules so you don't have to write the match.

The Intuition

`transpose()` swaps the `Result` and `Option` wrappers — commuting the two layers in a predictable way that preserves all information.

How It Works in Rust

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

ok_some.transpose()  // => Some(Ok(42))   — success with a value
ok_none.transpose()  // => None            — success with no value (becomes None)
err.transpose()      // => Some(Err("bad")) — failure becomes Some(Err)

// Option::transpose() goes the other direction:
let some_ok: Option<Result<i32, &str>> = Some(Ok(5));
let some_err: Option<Result<i32, &str>> = Some(Err("fail"));
let none: Option<Result<i32, &str>> = None;

some_ok.transpose()  // => Ok(Some(5))    — value present, no error
some_err.transpose() // => Err("fail")    — error propagates out
none.transpose()     // => Ok(None)       — absent is treated as success with no value

// Practical: parse an optional config value cleanly
let config_val: Option<&str> = Some("42");
let result: Result<Option<i32>, _> = config_val
 .map(|s| s.parse::<i32>())  // Option<Result<i32, ParseIntError>>
 .transpose();                // Result<Option<i32>, ParseIntError>
The `Option::transpose()` direction is the more commonly useful one — it lets you use `?` on an `Option<Result<T,E>>` after transposing.

What This Unlocks

Key Differences

ConceptOCamlRust
`Ok(None)` → `None`Manual match`Result::transpose()`
`Ok(Some(v))` → `Some(Ok(v))`Manual match`Result::transpose()`
`Some(Ok(v))` → `Ok(Some(v))`Manual match`Option::transpose()`
Use caseManual unwrappingComposing optional + fallible operations
//! 301. Converting Result<Option<T>> into Option<Result<T>>
//!
//! `Result::transpose()` swaps `Result` and `Option` layers.

fn maybe_parse(s: Option<&str>) -> Result<Option<i32>, std::num::ParseIntError> {
    match s {
        None => Ok(None),
        Some(s) => s.parse::<i32>().map(Some),
    }
}

fn main() {
    // Result::transpose()
    let ok_some: Result<Option<i32>, &str> = Ok(Some(42));
    let ok_none: Result<Option<i32>, &str> = Ok(None);
    let err:     Result<Option<i32>, &str> = Err("bad");

    println!("Ok(Some(42)).transpose() = {:?}", ok_some.transpose());
    println!("Ok(None).transpose()     = {:?}", ok_none.transpose());
    println!("Err(...).transpose()     = {:?}", err.transpose());

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

    println!("Some(Ok(5)).transpose()   = {:?}", some_ok.transpose());
    println!("Some(Err).transpose()     = {:?}", some_err.transpose());
    println!("None.transpose()          = {:?}", none.transpose());

    // Practical: parse optional config value
    let config_val: Option<&str> = Some("42");
    let parsed: Option<Result<i32, _>> = config_val.map(|s| s.parse::<i32>());
    let transposed: Result<Option<i32>, _> = parsed.transpose();
    println!("Config parse transposed: {:?}", transposed);
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_result_transpose_ok_some() {
        let r: Result<Option<i32>, &str> = Ok(Some(42));
        assert_eq!(r.transpose(), Some(Ok(42)));
    }

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

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

    #[test]
    fn test_option_transpose() {
        let o: Option<Result<i32, &str>> = Some(Ok(5));
        assert_eq!(o.transpose(), Ok(Some(5)));
    }
}
(* 301. Converting Result<Option<T>> into Option<Result<T>> - OCaml *)

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

let () =
  let r1 : (int option, string) result = Ok (Some 42) in
  let r2 : (int option, string) result = Ok None in
  let r3 : (int option, string) result = Error "bad" in
  Printf.printf "Ok(Some(42)) -> %s\n"
    (match transpose_result_option r1 with
     | Some (Ok n) -> Printf.sprintf "Some(Ok(%d))" n
     | _ -> "other");
  Printf.printf "Ok(None) -> %s\n"
    (match transpose_result_option r2 with None -> "None" | _ -> "Some");
  Printf.printf "Err -> %s\n"
    (match transpose_result_option r3 with
     | Some (Error e) -> Printf.sprintf "Some(Err(%s))" e
     | _ -> "other")