Error Handling

and_then() and or_else()

and_then() and or_else() are chaining combinators on Option<T> and Result<T, E> that invoke a closure returning another Option or Result without nesting the wrapper type.

Why and_then() Avoids Nested Wrappers

When a closure passed to map() returns an Option<U> or Result<U, E>, the result is nested (Option<Option<U>> or Result<Result<U, E>, E>). and_then() (known as flatMap or bind in other languages) flattens that step so the output remains a single Option<U> or Result<U, E>:

Some("42").map(|s| s.parse::<i32>().ok())      --> Some(Some(42))   (nested)
Some("42").and_then(|s| s.parse::<i32>().ok()) --> Some(42)         (flattened)
Method Receiver Closure Signature Runs Closure When Short-Circuits When
opt.and_then(f) Option<T> FnOnce(T) -> Option<U> Some(v) None (returns None)
res.and_then(f) Result<T, E> FnOnce(T) -> Result<U, E> Ok(v) Err(e) (returns Err(e))
opt.or_else(f) Option<T> FnOnce() -> Option<T> None Some(v) (returns Some(v))
res.or_else(f) Result<T, E> FnOnce(E) -> Result<T, F> Err(e) Ok(v) (returns Ok(v))

Chaining Dependent Operations with and_then()

Use and_then() to pipeline sequential validation or lookup steps where each step depends on the unwrapped output of the previous step and can fail independently:

fn parse_port(s: &str) -> Result<u16, String> {
    s.trim()
        .parse::<u16>()
        .map_err(|e| format!("invalid integer: {e}"))
}

fn require_unprivileged(port: u16) -> Result<u16, String> {
    if port >= 1024 {
        Ok(port)
    } else {
        Err(format!("port {port} is privileged (< 1024)"))
    }
}

fn validate_port(input: &str) -> Result<u16, String> {
    parse_port(input).and_then(require_unprivileged)
}

fn main() {
    for raw in ["8080", "80", "abc"] {
        println!("{raw:?} -> {:?}", validate_port(raw));
    }
}
"8080" -> Ok(8080)
"80" -> Err("port 80 is privileged (< 1024)")
"abc" -> Err("invalid integer: invalid digit found in string")

On Option<T>, and_then() cleanly chains optional lookups and slices:

fn extract_domain_tld(email: &str) -> Option<&str> {
    email
        .split_once('@')
        .map(|(_, domain)| domain)
        .and_then(|domain| domain.rsplit_once('.'))
        .map(|(_, tld)| tld)
}

fn main() {
    println!("{:?}", extract_domain_tld("user@example.org"));
    println!("{:?}", extract_domain_tld("user@localhost"));
    println!("{:?}", extract_domain_tld("invalid-email"));
}
Some("org")
None
None

Recovering or Falling Back with or_else()

or_else() is the dual of and_then(): it runs its closure only when the receiver is None or Err(e), allowing you to try backup sources or recover from specific errors before giving up:

fn primary_lookup(key: &str) -> Result<u32, &'static str> {
    if key == "primary" {
        Ok(100)
    } else {
        Err("missing in primary")
    }
}

fn replica_lookup(key: &str) -> Result<u32, &'static str> {
    if key == "replica" {
        Ok(200)
    } else {
        Err("missing in both primary and replica")
    }
}

fn fetch_with_fallback(key: &str) -> Result<u32, &'static str> {
    primary_lookup(key).or_else(|_| replica_lookup(key))
}

fn main() {
    for key in ["primary", "replica", "unknown"] {
        println!("{key}: {:?}", fetch_with_fallback(key));
    }
}
primary: Ok(100)
replica: Ok(200)
unknown: Err("missing in both primary and replica")