🎓 A1Learner
🔍
0 of 142 lessons completed

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?