Error Handling

Propagating Errors

Error propagation is the practice of returning an error from a function to its caller instead of handling or panicking on the failure locally. By returning Result<T, E>, a lower-level function reports what went wrong while leaving the recovery policy—such as retrying, substituting a default, or reporting a message to the user—to the caller that has enough context to decide.

Call-Stack Flow During Propagation

main()
  └── calls parse_config("port=abc")
        └── calls parse_u16("abc")
              └── returns Err(ParseIntError)
        └── propagates Err(ParseIntError) immediately via `?`
  └── matches Err(e) and logs a fallback message

Manual Propagation vs. the ? Operator

You can propagate errors explicitly with match or let ... else, or concisely with the ? operator:

Technique Syntax When to Use
? operator let val = step()?; Standard propagation when From can convert the error (or types already match)
let ... else let Ok(val) = step() else { return Err(...); }; When you need to log, clean up, or build a custom error before returning
match match step() { Ok(v) => v, Err(e) => return Err(e) } When both Ok and Err arms require multi-line custom logic
use std::num::ParseIntError;

fn parse_port(raw: &str) -> Result<u16, ParseIntError> {
    let trimmed = raw.trim();
    let port: u16 = trimmed.parse()?;
    Ok(port)
}

fn build_endpoint(host: &str, raw_port: &str) -> Result<String, ParseIntError> {
    let port = parse_port(raw_port)?;
    Ok(format!("{host}:{port}"))
}

fn main() {
    for input in ["8080", " 443 ", "invalid"] {
        match build_endpoint("127.0.0.1", input) {
            Ok(addr) => println!("Endpoint ready: {addr}"),
            Err(err) => println!("Invalid port {input:?}: {err}"),
        }
    }
}
Endpoint ready: 127.0.0.1:8080
Endpoint ready: 127.0.0.1:443
Invalid port "invalid": invalid digit found in string

Propagating Errors Out of Iterator Pipelines

Inside a closure passed to Iterator::map, applying ? returns early from the closure itself rather than the outer function. To propagate the first error out of an iterator pipeline, collect the iterator of Result<T, E> items into Result<Vec<T>, E>—which short-circuits on the first Err(E):

use std::num::ParseIntError;

fn parse_all_Ids(inputs: &[&str]) -> Result<Vec<u32>, ParseIntError> {
    inputs.iter().map(|s| s.trim().parse::<u32>()).collect()
}

fn main() {
    println!("All valid: {:?}", parse_all_Ids(&["10", "20", "30"]));
    println!("One bad:   {:?}", parse_all_Ids(&["10", "twenty", "30"]));
}
All valid: Ok([10, 20, 30])
One bad:   Err(ParseIntError { kind: InvalidDigit })