The ? Operator
? early-returns Err (or None in Option contexts) and unwraps Ok values, with automatic conversion via From.
Recipe
fn load_config(path: &str) -> Result<String, std::io::Error> {
let body = std::fs::read_to_string(path)?;
Ok(body)
}When to reach for this: Fallible functions returning Result - preferred over nested match.
Working Example
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From<std::num::ParseIntError> for AppError {
fn from(e: std::num::ParseIntError) -> Self { AppError::Parse(e) }
}
fn port_from_file(path: &str) -> Result<u16, AppError> {
let text = std::fs::read_to_string(path)?;
let port: u16 = text.trim().parse()?;
Ok(port)
}What this demonstrates:
?convertsio::ErrorandParseIntErrorintoAppErrorviaFrom- Happy path stays linear
- Return type must implement
Fromfor each error source
Deep Dive
? on Result in functions returning Result expands to match with early return. In main, use fn main() -> Result<(), E>.
try_blocks (where stable) group fallible steps - check edition.
Gotchas
?inmainwithout return type - Error. Fix:fn main() -> Result<(), Box<dyn Error>>.- Missing
Fromconversion - Compile error. Fix:impl Fromor map manually. ?onOptioninResultfn - Needs compatible types orok_or. Fix:ok_or(Error::Missing)?.- Swallowing context - Bare
?loses location. Fix:anyhow::Contextormap_err. - Using
?in closures - Closure must returnResult. Fix: Change closure return type.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
match | Complex recovery per error | Simple propagation |
map_err | Transform error | From already correct |
unwrap | Prototype only | Library/production |
let-else | Option guard | Result propagation |
FAQs
Performance?
Same as match - zero cost.try operator on Option?
In Option-returning functions only.Multiple error types?
Custom enum + `From` impls.async ?
Works in async fn returning `Result`.Closure ?
Requires `Result` return on closure.try blocks?
Experimental/stable per version - check docs.? in const fn?
Limited - growing const support.Inspect before ?
`.inspect_err()` (1.76+) log then ?.anyhow?
Single `?` path with `anyhow::Result`.thiserror?
Derive `From` for library errors.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.