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")