Result

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