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: Returns f(val) if Some(val), or default (evaluated eagerly) if None.
  • opt.map_or_else(default_fn, f) -> U: Returns f(val) if Some(val), or calls default_fn() lazily if None.