Error Handling
Result and Option Combinators
Combinators on Option<T> and Result<T, E> are method adapters that filter, combine, inspect, flatten, or convert between optional and fallible values without writing multi-step match expressions.
Combinator Reference
| Category | Method | Signature Conversion |
|---|---|---|
| Option -> Result | opt.ok_or(err) / opt.ok_or_else(f) |
Option<T> -> Result<T, E> |
| Result -> Option | res.ok() / res.err() |
Result<T, E> -> Option<T> / Option<E> |
| Transposition | opt.transpose() / res.transpose() |
Option<Result<T, E>> <-> Result<Option<T>, E> |
| Filtering | opt.filter(predicate) |
Option<T> -> Option<T> (None if predicate is false) |
| Pairing | opt_a.zip(opt_b) |
(Option<A>, Option<B>) -> Option<(A, B)> |
| Boolean Algebra | a.and(b) / a.or(b) / a.xor(b) |
Combines two Option or Result values |
| Side-Effect Inspection | inspect(f) / inspect_err(f) |
Calls f(&v) or f(&e) for logging and returns self unchanged |
| Flattening | nested.flatten() |
Option<Option<T>> -> Option<T> |
Bridging Between Option<T> and Result<T, E>
Option<T> ── .ok_or_else(|| err) ──> Result<T, E>
Result<T, E> ── .ok() ──> Option<T> (discards Err)
Option<Result<T, E>> <── .transpose() ──> Result<Option<T>, E>
transpose() is especially useful when an optional field (such as Option<&str>) needs to be parsed with a fallible function (str::parse), turning Option<Result<T, E>> into Result<Option<T>, E> so ? can propagate a parse error while preserving None:
use std::num::ParseIntError;
fn parse_optional_port(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
raw.map(|s| s.trim().parse::<u16>()).transpose()
}
fn main() {
println!("None: {:?}", parse_optional_port(None));
println!("Some valid: {:?}", parse_optional_port(Some("8080")));
println!("Some broken: {:?}", parse_optional_port(Some("bad")));
}
None: Ok(None)
Some valid: Ok(Some(8080))
Some broken: Err(ParseIntError { kind: InvalidDigit })
Filtering and Zipping Option Values
filter(|v| ...)keepsSome(v)if the predicate returnstrue, or turns it intoNone.zip(other)pairsSome(a)andSome(b)intoSome((a, b)), returningNoneif either side isNone.
fn build_coordinates(lat: Option<f64>, lon: Option<f64>) -> Option<(f64, f64)> {
let valid_lat = lat.filter(|&y| (-90.0..=90.0).contains(&y));
let valid_lon = lon.filter(|&x| (-180.0..=180.0).contains(&x));
valid_lat.zip(valid_lon)
}
fn main() {
println!("Valid pair: {:?}", build_coordinates(Some(23.62), Some(90.50)));
println!("Out of range: {:?}", build_coordinates(Some(120.0), Some(90.50)));
println!("Missing lon: {:?}", build_coordinates(Some(23.62), None));
}
Valid pair: Some((23.62, 90.5))
Out of range: None
Missing lon: None
Observing Values in Place with inspect() and inspect_err()
inspect() and inspect_err() take a shared reference (&T or &E) to log, trace, or assert on intermediate pipeline values without consuming or altering the Option or Result:
fn process_score(raw: &str) -> Result<u32, std::num::ParseIntError> {
raw.trim()
.parse::<u32>()
.inspect(|val| println!(" [trace] parsed raw score: {val}"))
.map(|val| val * 10)
.inspect_err(|err| println!(" [error] failed to parse {raw:?}: {err}"))
}
fn main() {
let _ = process_score(" 15 ");
let _ = process_score("invalid");
}
[trace] parsed raw score: 15
[error] failed to parse "invalid": invalid digit found in string