Result

236. Result::flatten — Collapse a Result<Result<T, E>, E> in One Call

Ended up with a Result<Result<T, E>, E> and reached for a nested match to peel it? When both layers share the same error type, Result::flatten (stable since 1.89) does it in one call.

How you get a doubly-wrapped Result

It usually sneaks in when you map a fallible operation over something that’s already a Result:

1
2
3
4
5
6
7
8
fn first_word(line: Result<&str, String>) -> Result<Result<u32, String>, String> {
    line.map(|s| {
        s.split_whitespace()
            .next()
            .ok_or_else(|| "empty line".to_string())
            .and_then(|w| w.parse::<u32>().map_err(|e| e.to_string()))
    })
}

The outer Result is “did we have a line?”, the inner one is “did it parse?”. Same String error on both — but the type is now Result<Result<u32, String>, String>, which no caller wants to touch.

The manual peel

1
2
3
4
5
6
7
8
let nested: Result<Result<u32, String>, String> = Ok(Ok(42));

let flat: Result<u32, String> = match nested {
    Ok(inner) => inner,   // inner is itself a Result
    Err(e) => Err(e),
};

assert_eq!(flat, Ok(42));

Correct, but it’s boilerplate that hides the intent.

flatten does exactly this

1
2
3
4
5
6
7
let ok:    Result<Result<u32, String>, String> = Ok(Ok(42));
let inner: Result<Result<u32, String>, String> = Ok(Err("bad".into()));
let outer: Result<Result<u32, String>, String> = Err("io".into());

assert_eq!(ok.flatten(),    Ok(42));
assert_eq!(inner.flatten(), Err("bad".to_string()));
assert_eq!(outer.flatten(), Err("io".to_string()));

Ok(Ok(x)) becomes Ok(x); either Err — inner or outer — passes straight through. Option has the same method, Option::flatten, for Option<Option<T>>.

The catch: error types must match

flatten is Result<Result<T, E>, E>Result<T, E> — one E. If the two layers carry different error types, flatten can’t merge them. Reach for and_then instead, which lets ?-style conversion do the work:

1
2
3
4
5
let nested: Result<Result<u32, String>, String> = Ok(Ok(7));

// same as .flatten() here, but and_then can also adapt the inner error
let flat = nested.and_then(|inner| inner);
assert_eq!(flat, Ok(7));

When the errors already line up, flatten is the clearest way to say “unwrap one layer of nesting.”

#226 Jun 2026

226. unwrap_or_default — Stop Spelling Out the Empty Value

Writing .unwrap_or(0) or .unwrap_or_else(String::new) to fall back to an empty value? If the type already has a Default, unwrap_or_default says it for you.

The fallback you keep typing out

You pull a value out of an Option, and the “missing” case is just the type’s natural zero: 0 for a number, "" for a string, [] for a vec. So you spell it out:

1
2
3
4
5
6
7
let count: Option<u32> = None;
let n = count.unwrap_or(0);
assert_eq!(n, 0);

let name: Option<String> = None;
let s = name.unwrap_or_else(String::new);
assert_eq!(s, "");

Every one of those fallbacks is just Default::default(). unwrap_or_default reaches for it directly — no literal to pick, no closure to write:

1
2
3
4
5
6
7
8
let count: Option<u32> = None;
assert_eq!(count.unwrap_or_default(), 0);

let name: Option<String> = None;
assert_eq!(name.unwrap_or_default(), "");

let items: Option<Vec<i32>> = None;
assert_eq!(items.unwrap_or_default(), Vec::<i32>::new());

When Some, you get the value untouched; when None, you get T::default().

Where it shines: map lookups

Counting with a HashMap is the classic case — a missing key should read as zero:

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

let mut counts: HashMap<&str, u32> = HashMap::new();
counts.insert("hits", 3);

let hits = counts.get("hits").copied().unwrap_or_default();
let misses = counts.get("misses").copied().unwrap_or_default();
assert_eq!(hits, 3);
assert_eq!(misses, 0);

No .unwrap_or(0) sprinkled at every call site, and if the value type changes, the default follows along automatically.

It works on Result too

Result::unwrap_or_default discards the Err and hands back the default — handy when a parse failure should just mean “nothing”:

1
2
3
4
let good = "42".parse::<i32>().unwrap_or_default();
let bad = "oops".parse::<i32>().unwrap_or_default();
assert_eq!(good, 42);
assert_eq!(bad, 0);

And on your own types

Derive Default and the same trick works for your structs — the fallback stays in one place instead of scattered across the codebase:

1
2
3
4
5
6
7
8
#[derive(Default, Debug, PartialEq)]
struct Config {
    retries: u32,
    verbose: bool,
}

let cfg: Option<Config> = None;
assert_eq!(cfg.unwrap_or_default(), Config { retries: 0, verbose: false });

Reach for unwrap_or_default whenever the fallback is the empty value — let the type decide what empty means.

#225 Jun 2026

225. Option::map_or — Transform-or-Default in One Call, Skip the match

Reaching for a four-line match just to turn an Option into a plain value? map_or does the transform and the fallback in a single call.

The match you keep rewriting

You have an Option, you want a concrete value: apply a function if it’s Some, fall back to a default if it’s None.

1
2
3
4
5
6
7
let name: Option<&str> = Some("ferris");

let len = match name {
    Some(n) => n.len(),
    None => 0,
};
assert_eq!(len, 6);

map_or(default, f) collapses both arms. The first argument is the None fallback, the second is what to do with the value inside Some:

1
2
3
4
5
let name: Option<&str> = Some("ferris");
assert_eq!(name.map_or(0, |n| n.len()), 6);

let missing: Option<&str> = None;
assert_eq!(missing.map_or(0, |n| n.len()), 0);

It beats the common .map(|n| n.len()).unwrap_or(0) too — same result, but no intermediate Option built just to immediately unwrap it.

The catch: the default is eager

map_or takes the default by value, so it’s computed whether or not you need it. With a cheap literal like 0 that’s free. With anything that allocates or does real work, you pay for it even on the Some path:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn expensive_default() -> String {
    // imagine a config read, an allocation, a computation...
    "fallback".to_string()
}

let port: Option<u16> = Some(8080);

// ⚠️ expensive_default() runs even though port is Some
let label = port.map_or(expensive_default(), |p| format!("port {p}"));
assert_eq!(label, "port 8080");

When the fallback isn’t free, switch to map_or_else, which takes a closure and only calls it on None:

1
2
3
4
5
6
7
fn expensive_default() -> String {
    "fallback".to_string()
}

let port: Option<u16> = None;
let label = port.map_or_else(|| expensive_default(), |p| format!("port {p}"));
assert_eq!(label, "fallback");

It works on Result too

Result::map_or follows the same shape — the value goes through f, any Err yields the default:

1
2
3
4
5
let parsed = "42".parse::<i32>();
assert_eq!(parsed.map_or(-1, |n| n * 2), 84);

let bad = "oops".parse::<i32>();
assert_eq!(bad.map_or(-1, |n| n * 2), -1);

Use map_or when the default is a cheap literal, map_or_else when it isn’t, and let the match go.

168. collect::<Result<Vec<_>, _>>() — Bail an Iterator on the First Error

You have a Vec<&str> of numbers to parse and you want to bail the moment one of them is malformed. The hand-rolled loop with ? works, but collect() does the same thing in one line — and most people never realize the trick is in the turbofish.

The pain point is everywhere: parse a row of CSV cells, deserialize a batch of records, convert a list of paths — any time you map a fallible function over an iterator and want the first failure to stop the show.

The naive version builds the Vec by hand and threads ? through a loop:

1
2
3
4
5
6
7
fn parse_all_loop(inputs: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
    let mut nums = Vec::with_capacity(inputs.len());
    for s in inputs {
        nums.push(s.parse::<i32>()?);
    }
    Ok(nums)
}

The same thing in one line, by asking collect to produce a Result<Vec<_>, _> instead of a Vec<Result<_, _>>:

1
2
3
fn parse_all(inputs: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
    inputs.iter().map(|s| s.parse::<i32>()).collect()
}

That works because Result<C, E> (where C: FromIterator<T>) implements FromIterator<Result<T, E>>: feed it an iterator of Results and it yields a single Result. The first Err short-circuits the rest of the iteration; if every item is Ok, you get Ok(collection) back.

1
2
3
4
5
6
7
8
let good = ["1", "2", "3"];
let bad  = ["1", "oops", "3"];

let ok: Result<Vec<i32>, _> = good.iter().map(|s| s.parse()).collect();
assert_eq!(ok.unwrap(), vec![1, 2, 3]);

let err: Result<Vec<i32>, _> = bad.iter().map(|s| s.parse()).collect();
assert!(err.is_err());

The same impl exists for Option, so collect::<Option<Vec<_>>>() bails on the first None:

1
2
3
4
5
let some_all: Option<Vec<u32>> = ["1", "2", "3"].iter().map(|s| s.parse().ok()).collect();
assert_eq!(some_all, Some(vec![1, 2, 3]));

let some_none: Option<Vec<u32>> = ["1", "no", "3"].iter().map(|s| s.parse().ok()).collect();
assert_eq!(some_none, None);

You’re not stuck with Vec either — any FromIterator target works, so Result<HashSet<_>, _> or Result<String, _> from a Chars iterator is just as valid.

Use it whenever you’d otherwise write a fold-the-error loop. The intent reads off the type signature, and the iterator stops cold at the first bad item instead of finishing the parse and then sifting through a Vec<Result<_, _>> afterwards.

148. Result::is_ok_and — Test the Variant and the Value in One Call

Checking “is this Ok, and is the inner value positive?” usually means a match or an if let. Result::is_ok_and collapses both questions into one line.

The pattern crops up everywhere: you have a Result, and you want a bool that says “yes, it succeeded and the value passes some test”. Without help, you write this:

1
2
3
4
5
6
7
let parsed: Result<i32, &str> = "42".parse().map_err(|_| "bad input");

let big_enough = match &parsed {
    Ok(n) => *n > 10,
    Err(_) => false,
};
assert!(big_enough);

It works, but five lines for what reads as one question is a lot. Result::is_ok_and takes a closure that runs only on the Ok value, and returns false for any Err:

1
2
let parsed: Result<i32, &str> = "42".parse().map_err(|_| "bad input");
assert!(parsed.is_ok_and(|n| n > 10));

The closure receives the value by move, so it works cleanly with references too:

1
2
let res: Result<String, ()> = Ok(String::from("rustbites"));
assert!(res.as_ref().is_ok_and(|s| s.starts_with("rust")));

There’s a mirror method for the failure path. Result::is_err_and runs the predicate only on the Err value:

1
2
3
4
5
let res: Result<i32, &str> = Err("missing field: name");
assert!(res.is_err_and(|e| e.contains("name")));

let ok: Result<i32, &str> = Ok(1);
assert!(!ok.is_err_and(|_| true));  // Ok short-circuits to false

Both methods short-circuit on the wrong variant without ever calling the closure, so you can put expensive checks in the predicate without worrying about wasted work on the unhappy path.

A nice place this shines is filtering an iterator of Results:

1
2
3
4
5
6
let inputs = ["12", "hello", "7", "0", "99"];
let count = inputs
    .iter()
    .filter(|s| s.parse::<i32>().is_ok_and(|n| n > 5))
    .count();
assert_eq!(count, 3);  // "12", "7", and "99"

Same trick exists on Option as is_some_and and is_none_or — small surface area, big readability win.

114. Option::transpose — Use ? on an Optional Result

Got an Option<Result<T, E>> and want to ? the error out? You can’t — ? doesn’t reach inside the Option. transpose flips it to Result<Option<T>, E>, and the rest takes care of itself.

The classic case: a config field that’s optional, but if it’s there, it has to parse. The old dance is three match arms just to thread the error out of the Option:

1
2
3
4
5
6
7
8
9
use std::num::ParseIntError;

fn parse_port_old(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    match raw.map(str::parse::<u16>) {
        Some(Ok(p))  => Ok(Some(p)),
        Some(Err(e)) => Err(e),
        None         => Ok(None),
    }
}

transpose collapses it:

1
2
3
4
5
use std::num::ParseIntError;

fn parse_port(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    raw.map(str::parse::<u16>).transpose()
}

Option<Result<T, E>>::transpose() returns Result<Option<T>, E> — exactly the shape ? wants. Now you can chain it inline:

1
2
3
4
5
6
7
use std::collections::HashMap;
use std::num::ParseIntError;

fn read_port(config: &HashMap<&str, &str>) -> Result<Option<u16>, ParseIntError> {
    let port = config.get("port").copied().map(str::parse::<u16>).transpose()?;
    Ok(port)
}

It works the other way too: Result<Option<T>, E>::transpose() returns Option<Result<T, E>>. Handy when an iterator chain wants the Option on the outside.

All three cases, one call:

1
2
3
assert_eq!(parse_port(Some("8080")).unwrap(), Some(8080));
assert_eq!(parse_port(None).unwrap(), None);
assert!(parse_port(Some("nope")).is_err());

Reach for transpose any time Option and Result get nested and you wish ? could see through both.

96. Result::inspect_err — Log Errors Without Breaking the ? Chain

You want to log an error but still bubble it up with ?. The usual trick is .map_err with a closure that sneaks in a eprintln! and returns the error unchanged. Result::inspect_err does that for you — and reads like what you meant.

The problem: logging mid-chain

You’re happily propagating errors with ?, but somewhere in the pipeline you want to peek at the failure — log it, bump a metric, attach context — without otherwise touching the value:

1
2
3
4
5
6
7
8
9
fn load_config(path: &str) -> Result<String, std::io::Error> {
    std::fs::read_to_string(path)
}

fn run(path: &str) -> Result<String, std::io::Error> {
    // We want to log the error here, but still return it.
    let cfg = load_config(path)?;
    Ok(cfg)
}

Adding a println! means breaking the chain, introducing a match, or writing a no-op map_err:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# fn load_config(path: &str) -> Result<String, std::io::Error> {
#     std::fs::read_to_string(path)
# }
fn run(path: &str) -> Result<String, std::io::Error> {
    let cfg = load_config(path).map_err(|e| {
        eprintln!("failed to read {}: {}", path, e);
        e  // have to return the error unchanged — easy to mess up
    })?;
    Ok(cfg)
}

That closure always has to end with e. Miss it and you’re silently changing the error type — or worse, swallowing it.

The fix: inspect_err

Stabilized in Rust 1.76, Result::inspect_err runs a closure on the error by reference and passes the Result through untouched:

1
2
3
4
5
6
7
8
# fn load_config(path: &str) -> Result<String, std::io::Error> {
#     std::fs::read_to_string(path)
# }
fn run(path: &str) -> Result<String, std::io::Error> {
    let cfg = load_config(path)
        .inspect_err(|e| eprintln!("failed to read config: {e}"))?;
    Ok(cfg)
}

The closure takes &E, so there’s nothing to return and nothing to get wrong. The value flows straight through to ?.

The Ok side too

There’s a mirror image for the happy path: Result::inspect. Same idea — &T in, nothing out, value preserved:

1
2
3
4
5
6
let parsed: Result<u16, _> = "8080"
    .parse::<u16>()
    .inspect(|port| println!("parsed port: {port}"))
    .inspect_err(|e| eprintln!("parse failed: {e}"));

assert_eq!(parsed, Ok(8080));

Both methods exist on Option too (Option::inspect) — handy when you want to trace a Some without consuming it.

Why it’s nicer than map_err

map_err is for transforming errors. inspect_err is for observing them. Using the right tool means:

  • No accidental error swallowing — the closure can’t return the wrong type.
  • The intent is obvious at a glance: this is a side effect, not a transformation.
  • It composes cleanly with ?, and_then, and the rest of the Result toolbox.
1
2
3
4
5
6
7
# fn load_config(path: &str) -> Result<String, std::io::Error> {
#     std::fs::read_to_string(path)
# }
// Chain several observations together without ceremony.
let _ = load_config("/does/not/exist")
    .inspect(|cfg| println!("loaded {} bytes", cfg.len()))
    .inspect_err(|e| eprintln!("load failed: {e}"));

Reach for inspect_err any time you’d otherwise write map_err(|e| { log(&e); e }) — you’ll have one less footgun and one less line.

84. Result::flatten — Unwrap Nested Results in One Call

You have a Result<Result<T, E>, E> and just want the inner Result<T, E>. Before Rust 1.89, that meant a clunky and_then(|r| r). Now there’s Result::flatten.

The problem: nested Results

Nested Results crop up naturally — call a function that returns Result, then map over the success with another fallible operation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn parse_port(s: &str) -> Result<u16, String> {
    s.parse::<u16>().map_err(|e| e.to_string())
}

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

let input = "8080";
let nested: Result<Result<u16, String>, String> =
    parse_port(input).map(|p| validate_port(p));
// nested is Ok(Ok(8080)) — awkward to work with

You end up with Ok(Ok(8080)) when you really want Ok(8080).

The old workaround

The standard trick was and_then with an identity closure:

1
2
3
4
5
6
7
8
# fn parse_port(s: &str) -> Result<u16, String> {
#     s.parse::<u16>().map_err(|e| e.to_string())
# }
# fn validate_port(port: u16) -> Result<u16, String> {
#     if port >= 1024 { Ok(port) } else { Err(format!("port {} is privileged", port)) }
# }
let flat = parse_port("8080").map(|p| validate_port(p)).and_then(|r| r);
assert_eq!(flat, Ok(8080));

It works, but .and_then(|r| r) is a puzzler if you haven’t seen the pattern before.

The fix: flatten

Stabilized in Rust 1.89, Result::flatten does exactly what you’d expect:

1
2
3
4
5
6
7
8
# fn parse_port(s: &str) -> Result<u16, String> {
#     s.parse::<u16>().map_err(|e| e.to_string())
# }
# fn validate_port(port: u16) -> Result<u16, String> {
#     if port >= 1024 { Ok(port) } else { Err(format!("port {} is privileged", port)) }
# }
let result = parse_port("8080").map(|p| validate_port(p)).flatten();
assert_eq!(result, Ok(8080));

If the outer is Err, you get that Err. If the outer is Ok(Err(e)), you get Err(e). Only Ok(Ok(v)) becomes Ok(v).

Error propagation still works

Both layers must share the same error type. The flattening preserves whichever error came first:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# fn parse_port(s: &str) -> Result<u16, String> {
#     s.parse::<u16>().map_err(|e| e.to_string())
# }
# fn validate_port(port: u16) -> Result<u16, String> {
#     if port >= 1024 { Ok(port) } else { Err(format!("port {} is privileged", port)) }
# }
// Outer error: parse fails
let r1 = parse_port("abc").map(|p| validate_port(p)).flatten();
assert!(r1.is_err());

// Inner error: parse succeeds, validation fails
let r2 = parse_port("80").map(|p| validate_port(p)).flatten();
assert_eq!(r2, Err("port 80 is privileged".to_string()));

// Both succeed
let r3 = parse_port("3000").map(|p| validate_port(p)).flatten();
assert_eq!(r3, Ok(3000));

When to use flatten vs and_then

If you’re writing .map(f).flatten(), you probably want .and_then(f) — it’s the same thing, one call shorter. flatten shines when you already have a nested Result and just need to collapse it — say, from a generic API, a deserialized value, or a collection of results mapped over a fallible function.

#016 Jun 2022

16. Option/Result match?!

Try to avoid matching Option or Result.

Use if let instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let result = Some(111);

// Not nice
match result {
    Some(x) => println!("{x}"),
    None => {}
};

// Better
if let Some(x) = result {
    println!("{x}");
}
#004 Jan 2022

4. Option -> Result

Convert Option to Result easily.

1
2
3
let o: Option<u32> = Some(200u32);
let r: Result<u32,()> = o.ok_or(());
assert_eq!(r, Ok(200u32));
#003 Jan 2022

3. Result -> Option

Convert Result to Option easily.

1
2
3
let r: Result<u32,()> = Ok(200u32);
let o: Option<u32> = r.ok();
assert_eq!(o, Some(200u32));