Rust Option & Result Combinators
Combinators are small methods that handle an Option or a Result one step at a time. A chain replaces the nested matches that step-by-step handling would need.
The long way: a match for one step
Handling one step with a full match spells out both cases:
let start = Some(3);
let doubled = match start {
Some(n) => Some(n * 2),
None => None,
};
println!("{doubled:?}");map changes the value inside
map runs your closure on the value in the box, and hands the box back.
let doubled = Some(3).map(|n| n * 2);
println!("{doubled:?}");and_then keeps it one layer flat
When the step itself returns an Option, and_then feeds the value straight through — no box in a box.
fn halve_if_even(n: i32) -> Option<i32> {
if n % 2 == 0 { Some(n / 2) } else { None }
}
let flat = Some(10).and_then(halve_if_even);
println!("{flat:?}");flatten removes the extra layer
Already holding a box in a box? flatten collapses it to one layer.
let nested = Some(Some(5));
println!("{:?}", nested.flatten());map_err changes only the error
map_err rewrites the error and leaves an Ok value alone.
let r: Result<i32, &str> = Err("bad");
let fixed = r.map_err(|e| format!("failed: {e}"));
println!("{fixed:?}");An Option joins a list like a tiny container
extend accepts the Option itself: Some adds its value, None would add nothing.
let extra = Some("Turing");
let mut names = vec!["Curry", "Kleene"];
names.extend(extra);
println!("{names:?}");Example
fn halve_if_even(n: i32) -> Option<i32> {
if n % 2 == 0 { Some(n / 2) } else { None }
}
fn main() {
let nested = Some(10).map(|n| halve_if_even(n));
println!("nested = {:?}", nested);
let flat = Some(10).and_then(|n| halve_if_even(n));
println!("flat = {:?}", flat);
let collapsed = nested.flatten();
println!("collapsed = {:?}", collapsed);
let twice = Some(84).and_then(halve_if_even).and_then(halve_if_even);
println!("halved twice = {:?}", twice);
let product = "20".parse::<i32>().and_then(|n| "3".parse::<i32>().map(|m| n * m));
println!("product = {:?}", product);
}Key points
- A combinator is a method that takes a closure and handles the Some/Ok and None/Err cases for you
- Result has the same family: map and and_then work on the Ok side, map_err changes only the error
In this example, halve_if_even returns an Option: half the number when it is even, None when it is odd. Calling it through map wraps its answer in another Some, so nested prints Some(Some(5)) — a box in a box. and_then feeds the value straight through, so flat prints Some(5), one layer only. flatten removes the extra layer from nested, giving Some(5) again. Chaining and_then twice halves 84 to 42, then to 21. Last, parse returns a Result, and its and_then chains a second parse and a map that multiplies: Ok(60).
Note
A chain reads top to bottom, in the order the steps really happen.
A nested match reads inside-out, which is why chains are easier to follow.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Your next step returns an Option, and map would give you Option<Option<T>>. What should you use instead?