Option<T> and Result<T, E> in Depth
map() and and_then()
The .map() and .and_then() combinators transform the inner value of an Option<T> (or Result<T, E>) when present, short-circuiting and propagating None (or Err(e)) automatically when absent.
Signatures and When to Use Each
impl<T> Option<T> {
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U>;
pub fn and_then<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<U>;
}
| Combinator | Closure Signature | Closure Return | Overall Return | Use When... |
|---|---|---|---|---|
opt.map(f) |
FnOnce(T) -> U |
Plain value U (infallible) |
Option<U> |
The transformation on T always succeeds (T -> U) |
opt.and_then(f) |
FnOnce(T) -> Option<U> |
Optional value Option<U> (fallible) |
Option<U> (flattened!) |
The transformation on T can itself return None (T -> Option<U>) |
1. .map(|x| x * 2):
Some(21) ──> f(21) = 42 ──> Wraps in Some(42)
None ──> (f not called) ──> None
2. .and_then(|s| s.parse::<i32>().ok()):
Some("42") ──> f("42") = Some(42) ──> Returns Some(42) directly (avoids Option<Option<i32>>!)
Some("bad") ──> f("bad") = None ──> Returns None
None ──> (f not called) ──> Returns None
If you mistakenly pass a function returning Option<U> to .map(f), the result is a nested Option<Option<U>>. Using .and_then(f) (also called flatMap or monadic bind in other languages) flattens the result into Option<U>.
Chaining .and_then() and .map() Without Nested match Blocks
struct UserProfile {
email: Option<String>,
}
fn find_user(id: u32) -> Option<UserProfile> {
match id {
1 => Some(UserProfile {
email: Some(String::from("alice@example.com")),
}),
2 => Some(UserProfile { email: None }),
_ => None,
}
}
fn extract_domain(email: &str) -> Option<&str> {
email.split_once('@').map(|(_local, domain)| domain)
}
fn user_email_domain_uppercase(user_id: u32) -> Option<String> {
find_user(user_id)
.and_then(|profile| profile.email) // Option<UserProfile> -> Option<String>
.and_then(|email| extract_domain(&email).map(str::to_owned)) // -> Option<String>
.map(|domain| domain.to_uppercase()) // Option<String> -> Option<String>
}
fn main() {
println!("User 1 domain: {:?}", user_email_domain_uppercase(1));
println!("User 2 domain: {:?}", user_email_domain_uppercase(2));
println!("User 9 domain: {:?}", user_email_domain_uppercase(9));
}
User 1 domain: Some("EXAMPLE.COM")
User 2 domain: None
User 9 domain: None
Transforming With a Default Fallback (map_or and map_or_else)
When you want to apply a .map(f) transformation and unwrap with a fallback value in one step:
opt.map_or(default, f) -> U: Returnsf(val)ifSome(val), ordefault(evaluated eagerly) ifNone.opt.map_or_else(default_fn, f) -> U: Returnsf(val)ifSome(val), or callsdefault_fn()lazily ifNone.