---
title: "12.3 Propagating errors: the ? operator"
description: "The ? operator turns pages of match-on-Result boilerplate into one character: it returns the error early on Err and unwraps the value on Ok."
url: "https://learnrust.net/chapter-12/propagating-errors-question-mark-operator/"
last_updated: "2026-06-13"
---

# 12.3 Propagating errors: the ? operator

A function that does several fallible things in a row, parse this, then parse that, then combine them, faces the same chore at every step: check the `Result`, and if it's an `Err`, stop and hand the error back to the caller. Written with `match`, this buries the actual logic under failure-handling. The `?` operator exists to make it vanish. It is one of the most-loved pieces of Rust syntax, and by the end of this lesson you'll see why.

## The problem: match pyramids

Here's a function that parses two strings and adds them, handling failure properly with `match`:

```rust
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> {
    let first = match a.parse::<i32>() {
        Ok(n) => n,
        Err(e) => return Err(e),
    };
    let second = match b.parse::<i32>() {
        Ok(n) => n,
        Err(e) => return Err(e),
    };
    Ok(first + second)
}

fn main() {
    println!("{:?}", add_strings("3", "4"));     // Ok(7)
    println!("{:?}", add_strings("3", "oops"));  // Err(...)
}
```

```
Ok(7)
Err(ParseIntError { kind: InvalidDigit })
```

It works, but look at the shape. Each parse is wrapped in a `match` whose only job is "on `Ok`, keep the value; on `Err`, return it to the caller." That pattern, *propagating* the error upward, is so common that drowning the real work (`first + second`) in it is a genuine readability problem. The two `match` blocks say nothing interesting; they're pure plumbing.

## The fix: ?

The `?` operator is that exact pattern, as one character. Place `?` after an expression that returns a `Result`, and it does this: if the value is `Ok(v)`, it evaluates to `v` and execution continues; if the value is `Err(e)`, it *returns* `Err(e)` from the enclosing function immediately. The same function, rewritten:

```rust
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> {
    let first = a.parse::<i32>()?;
    let second = b.parse::<i32>()?;
    Ok(first + second)
}

fn main() {
    println!("{:?}", add_strings("3", "4"));     // Ok(7)
    println!("{:?}", add_strings("3", "oops"));  // Err(...)
}
```

```
Ok(7)
Err(ParseIntError { kind: InvalidDigit })
```

Identical behavior, and now the logic is visible. `a.parse::<i32>()?` means "parse, and if it failed, return that error from `add_strings` right now; otherwise give me the number." The two `?`s replace the two `match` blocks exactly, and the happy path reads like there were no errors to worry about at all, while every failure still gets propagated faithfully. This before-and-after is the whole lesson: `?` is sugar for "match, and `return Err` on failure."

> **Key insight**
>
> `?` is early-return-on-error in one character. `expr?` unwraps an `Ok` to its value or returns the `Err` from the current function. It turns the question "did this fail?" from something you write out at every step into something the `?` asks for you, so the function body shows the success path plainly and the errors route themselves upward. This is why idiomatic Rust is full of `?` and nearly free of `match`-on-`Result` plumbing.

## ? only works in functions that return Result

There's a catch that the compiler enforces, and it follows directly from what `?` does. If `?` returns an `Err` from the current function, that function must be *able* to return an `Err`, which means its return type must be a `Result` (or `Option`, or another compatible type). Use `?` in a function that returns plain `i32` and the compiler stops you:

```rust
fn bad(a: &str) -> i32 {
    let n = a.parse::<i32>()?;
    n * 2
}
```

```
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
 --> src/main.rs:2:30
  |
1 | fn bad(a: &str) -> i32 {
  |  ---------------------- this function should return `Result` or `Option` to accept `?`
2 |     let n = a.parse::<i32>()?;
  |                              ^ cannot use the `?` operator in a function that returns `i32`
```

The fix is to make `bad` return `Result<i32, ...>`. The error message even points at the signature as the thing to change. This isn't `?` being fussy; it's the type system insisting that "this function can produce an error" be written in the function's type, the same honesty `Result` brought in the first place.

## main can return Result

This raises a question: the error has to go *somewhere*. Each function passes it to its caller, but what about `main`, which has no caller? You can let `main` itself return a `Result`, and then `?` works at the top level too:

```rust
use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
    let n: i32 = "42".parse()?;
    println!("parsed {n}");
    Ok(())
}
```

```
parsed 42
```

A `main` returning `Result<(), E>` succeeds with `Ok(())` (the unit `()` from lesson [1.11](https://learnrust.net/chapter-1/expressions-and-statements/), meaning "nothing to return, it just worked") and may use `?` in its body. If `main` returns an `Err`, the program prints the error's debug form and exits with a failure code (lesson [7.8](https://learnrust.net/chapter-7/halting-your-program-early/)). This is the clean alternative to `unwrap` in `main`: instead of panicking on the first error, `main` can `?`-propagate it and let the runtime report it. We'll lean on this in the next lesson.

> **Tip**
>
> `?` also works on `Option`: `expr?` returns `None` early if `expr` is `None`, or unwraps the `Some`. It's the same idea applied to absence instead of failure, and it only works in a function returning `Option`. So `?` is the universal "unwrap or bail to the caller" operator for both of chapter 11's fallible enums.

## Quiz time

**Question #1**

In one sentence, what does `expr?` do when `expr` is `Ok(v)`, and what does it do when `expr` is `Err(e)`?

<details class="solution">
<summary>Show solution</summary>

When `expr` is `Ok(v)`, `expr?` evaluates to `v` and execution continues; when `expr` is `Err(e)`, `expr?` returns `Err(e)` from the enclosing function immediately. It's unwrap-or-return-the-error.

</details>

**Question #2**

Why won't this compile, and what's the fix?

```rust
fn first_number(text: &str) -> i32 {
    let n = text.parse::<i32>()?;
    n
}
```

<details class="solution">
<summary>Show solution</summary>

`?` can only be used in a function that returns `Result` (or `Option`), because on `Err` it returns the error from the function, and `i32` can't hold an error (E0277). Fix the signature: `fn first_number(text: &str) -> Result<i32, std::num::ParseIntError>`, and return `Ok(n)` at the end.

</details>

**Question #3**

Rewrite this using `?`:

```rust
fn parse_pair(a: &str, b: &str) -> Result<(i32, i32), std::num::ParseIntError> {
    let x = match a.parse::<i32>() {
        Ok(n) => n,
        Err(e) => return Err(e),
    };
    let y = match b.parse::<i32>() {
        Ok(n) => n,
        Err(e) => return Err(e),
    };
    Ok((x, y))
}
```

<details class="solution">
<summary>Show solution</summary>

```rust
fn parse_pair(a: &str, b: &str) -> Result<(i32, i32), std::num::ParseIntError> {
    let x = a.parse::<i32>()?;
    let y = b.parse::<i32>()?;
    Ok((x, y))
}
```

Each `match` becomes a `?`. The behavior is identical: any parse failure returns the error early, and the success path builds the tuple.

</details>

You now have the full mechanism: panic for bugs, `Result` for recoverable failure, `?` to propagate it cleanly. The next lesson spends it on a problem you've had an IOU on since chapter 7: turning the guessing game's rude `expect` crash into a polite "try again" loop.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
