SyntaxStudy
Sign Up
Rust The ? Operator and Error Propagation
Rust Beginner 1 min read

The ? Operator and Error Propagation

The `?` operator is the idiomatic way to propagate errors in Rust. When placed after an expression that returns `Result` or `Option`, it unwraps the success value if present, and returns early from the current function with the error value if not. For `Result`, it also calls `From::from` on the error type, enabling automatic conversion between error types as errors propagate up the call stack. Before the `?` operator was introduced (it replaced the `try!` macro in Rust 2018), error propagation required explicit `match` or `unwrap` calls, producing verbose, noisy code. With `?`, error-handling code looks almost as clean as code that ignores errors, yet is fully explicit about which operations can fail and propagates errors correctly without any boilerplate. The `?` operator can only be used in functions that return `Result` or `Option`. In `main`, you can use `?` by declaring `main` to return `Result<(), Box>`. For library code, it is better to define specific error types (often using the `thiserror` crate) and implement `From` conversions to allow `?` to work across error type boundaries.
Example
use std::fs;
use std::io;
use std::path::Path;

// Chain multiple fallible operations with ?
fn read_number_from_file(path: &Path) -> Result<i64, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;    // io::Error -> Box<dyn Error>
    let n: i64 = content.trim().parse()?;        // ParseIntError -> Box<dyn Error>
    Ok(n)
}

fn compute(path: &Path) -> Result<i64, Box<dyn std::error::Error>> {
    let n = read_number_from_file(path)?;
    if n < 0 {
        return Err("number must be non-negative".into());
    }
    Ok(n * n)
}

// main can return Result — ? works here too
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Write a temp file for demonstration
    let path = Path::new("number.txt");
    fs::write(path, "42\n")?;

    match compute(path) {
        Ok(v) => println!("42 squared = {v}"),
        Err(e) => println!("error: {e}"),
    }

    // Try a bad file
    let bad = Path::new("missing.txt");
    match compute(bad) {
        Ok(v) => println!("got {v}"),
        Err(e) => println!("expected error: {e}"),
    }

    // Cleanup
    fs::remove_file(path)?;
    Ok(())
}