C.W.K.
Stream
Lesson 03 of 05 · published

The ? Operator

~11 min · errors, question-mark, propagation, from

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

The ? operator is the single feature that makes Rust error handling pleasant. It turns the verbose match-and-bail pattern into one character.

What ? does

Put ? after a Result (or Option): if it's Ok(v), the expression evaluates to v and execution continues; if it's Err(e), the function returns early with that error. One operator replaces the whole 'match, and on Err return Err' dance — and you can chain several on one line.

It converts errors automatically

Here's the magic that ties back to the Traits track: when the error you're propagating differs from your function's declared error type, ? calls From::from to convert it. Implement From<LowLevelError> for MyError once, and ? seamlessly bubbles low-level errors up as your error type. This is why From for error types is worth the small effort.

? is early-return for errors, with free conversion. On Ok it unwraps and continues; on Err it returns, converting via From on the way out. The happy path reads top to bottom with no error-handling noise, and failures still can't be ignored.

The function must return Result

? only works in a function that returns Result (or Option, or another type implementing Try). It needs somewhere to return the error to. main itself can return Result<(), E> so you can use ? right at the top level.

Code

? replaces match-and-bail·rust
use std::num::ParseIntError;

// Without ?: nested matches. With ?: one clean line each.
fn sum_two(a: &str, b: &str) -> Result<i32, ParseIntError> {
    let x = a.trim().parse::<i32>()?; // Ok -> unwrap; Err -> return early
    let y = b.trim().parse::<i32>()?;
    Ok(x + y)
}

fn main() -> Result<(), ParseIntError> {
    println!("{}", sum_two("20", "22")?); // 42
    // sum_two("x", "1")?; // would propagate the parse error out of main
    Ok(())
}

External links

Exercise

Rewrite the sum_two function from a nested match version into a ? version. Confirm both compile to the same behavior. Then change main to return Result<(), ParseIntError> and propagate an error with ? straight out of main — what does the program print when it exits with an error?
Hint
When main returns Err, Rust prints the Debug representation of the error and exits with a non-zero code. That's why fn main() -> Result<(), E> is a handy top-level pattern for small programs.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.