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