• Option
• Result
• 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
• Option
• Result
• 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
// 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."
| Concept | OCaml | Rust | ||
|---|---|---|---|---|
| `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 propagation | Manual fold | `filter_map( | o | o.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")