Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Types and Error Handling  ›  Lesson

Propagating Errors with ?

Propagating Errors and `?` Operator 12 minThe ? operator unwraps an Ok or Some value and otherwise returns the error or None from the current function straight away.

Propagating Errors with ?

Reading a loan request means several steps that can fail: parse the card number, parse the number of days, look up the book. Writing a full match for each step buries the real logic. Usually the function should just stop and let its caller deal with the problem. Rust has a single character for that.

The rule

? unwraps success, or returns the failure from this function.

Without ?:

use std::num::ParseIntError;

fn parse_days(input: &str) -> Result<u32, ParseIntError> {
    let days = match input.trim().parse::<u32>() {
        Ok(d) => d,
        Err(e) => return Err(e),
    };
    Ok(days.min(21))
}

With ?, the same function:

fn parse_days(input: &str) -> Result<u32, ParseIntError> {
    let days = input.trim().parse::<u32>()?;
    Ok(days.min(21))
}

What ? does on a Result:

  • Ok(v) → the expression becomes v, and the function carries on.
  • Err(e) → the function returns immediately with Err(e). If the function's error type differs, ? first converts e with From::from (next lesson uses this).

On an Option, ? returns None early instead.

Where ? is allowed: only in a function whose return type can carry the failure: a Result for Result values, an Option for Option values. A plain fn main() returns (), so ? there is a compile error. The fix is to let main return a Result:

fn main() -> Result<(), ParseIntError> {
    let days = parse_days("14")?;
    println!("loan for {days} days");
    Ok(())
}

If main returns an Err, the program prints the error (in its Debug form) and exits with a failure code.

The shortcut

Read every ? aloud as "or bail out": "parse the days, or bail out". Then check the signature: can this function bail out with that kind of failure?

Parsing a loan requestStep 1 of 4
  1. Signature first

    fn parse_request(card: &str, days: &str) -> Result<(u32, u32), ParseIntError>: it can bail out with a ParseIntError.

The trap is forgetting the final Ok(...). The last line must wrap the success value, because the function returns a Result, not a bare tuple.

Drills

Answer fast — trust the rule.

quick drill

In let d = "14".parse::<u32>()?;, what is d when parsing succeeds?

quick drill

What does ? do when the parse returns Err?

quick drill

Why does ? inside a plain fn main() { ... } fail to compile?

quick drill

A function returns Option<Book>. What does ? on a None inside it do?

Remember

  • ? gives the success value or returns the failure early, converting the error with From if needed.
  • Read it as "or bail out", and check the function's return type can carry that failure.
  • Trap: ? in a function returning (), and forgetting the final Ok(...).

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainRewrite with ?

Explain what let card = input.trim().parse::<u32>()?; does in both the success and failure case, what the enclosing function's return type must be, and why using ? inside a fn main() with no return type fails.