---
title: "12.x Chapter 12 summary and quiz"
description: "Review of panic, Result, the ? operator, handling invalid input, when to panic, and custom error types, with a chapter quiz and a capstone program."
url: "https://learnrust.net/chapter-12/chapter-12-summary-and-quiz/"
last_updated: "2026-06-13"
---

# 12.x Chapter 12 summary and quiz

This chapter taught Rust's two-track approach to failure and the syntax that makes the recoverable track painless. Review, then a capstone that wires it all together.

## Quick review

Rust splits failure in two. **Unrecoverable** failures are bugs, broken assumptions, and they call for **`panic!`** ([12.1](https://learnrust.net/chapter-12/panic-and-unrecoverable-errors/)): it prints a message and location, unwinds the stack (dropping values as it goes), and stops the thread. `RUST_BACKTRACE=1` adds the call chain. `assert!`, `assert_eq!`, and `debug_assert!` are conditional panics for checking invariants, and `unwrap`/`expect` are convenience methods that panic when there's nothing to unwrap.

**Recoverable** failures are expected events, and they call for **`Result<T, E>`** ([12.2](https://learnrust.net/chapter-12/result/)), an enum with `Ok(T)` and `Err(E)`, handled with `match` like any chapter 11 enum. It's the type hiding inside every `parse` and `read_line` you've called; `.expect()` was treating its `Err` as a panic all along. `Result` makes failure a value you inspect, not an interruption, and exhaustiveness means you can't forget the error case.

The **`?` operator** ([12.3](https://learnrust.net/chapter-12/propagating-errors-question-mark-operator/)) is the propagation pattern in one character: `expr?` unwraps an `Ok` to its value or returns the `Err` from the current function. It only works where the function can return an error (`Result` or `Option`), and `main` itself can return `Result<(), E>` so `?` works at the top level. This turns match-pyramids into a flat, readable happy path.

Applying it: bad **user input** is the classic recoverable error ([12.4](https://learnrust.net/chapter-12/handling-invalid-input/)), so the guessing game's crashing `expect` becomes a `match` that prints a message and `continue`s. Input fails three ways, malformed, out of range, and extraction failure, and each deserves its own message. The judgment of **panic versus Result** ([12.5](https://learnrust.net/chapter-12/when-to-panic/)) comes down to: is the failure *expected* (the caller can recover, so return `Result`) or does it mean something is *already broken* (a bug, so panic)? Validate untrusted data at the boundary, treat contract violations as panics, and lean toward `Result` in reusable code. `unwrap`/`expect` are fine in tests, prototypes, and where `Err` is genuinely impossible (with `expect` documenting why).

Finally, **error types** ([12.6](https://learnrust.net/chapter-12/custom-error-types/)) are just types: an enum with a variant per failure kind lets callers `match` on what went wrong, `#[derive(Debug)]` gives the programmer's view, and an `impl` of `Display` (chapter 16's machinery, shown early) gives the human message. For applications that just need errors to flow, **`Box<dyn Error>`** is the catch-all that accepts `?` from any error type. Rust replaces C++'s entire exceptions chapter with "errors are values of types you define."

## Quiz time

**Question #1**

Give the one-question test for choosing between `panic!` and `Result`.

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

Could a well-written caller recover from this failure? If yes, the failure is expected, so return `Result` and let them decide. If no, it means an assumption is already broken (a bug), so `panic!` to stop cleanly. Equivalently: return `Result` when the caller is better placed than you to respond.

</details>

**Question #2**

For each, what happens?

a)

```rust
fn main() {
    let n: i32 = "7".parse().unwrap();
    println!("{n}");
}
```

b)

```rust
fn parse_two(a: &str, b: &str) -> i32 {
    let x = a.parse::<i32>()?;
    let y = b.parse::<i32>()?;
    x + y
}
```

c)

```rust
fn main() {
    let n: i32 = "seven".parse().unwrap();
    println!("{n}");
}
```

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

a) Prints `7`: `"7"` parses, `unwrap` returns the value. b) Refused, E0277: `?` is used in a function returning `i32`, not `Result`/`Option`; the fix is to return `Result<i32, _>` and `Ok(x + y)`. c) Panics: `"seven"` doesn't parse, and `unwrap` on the `Err` panics with `called Result::unwrap() on an Err value: ParseIntError ...`.

</details>

**Question #3**

Rewrite using `?` and a `main` that returns `Result`:

```rust
fn main() {
    let a: i32 = match "10".parse() {
        Ok(n) => n,
        Err(e) => {
            println!("error: {e}");
            return;
        }
    };
    println!("{}", a * 2);
}
```

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

```rust
fn main() -> Result<(), std::num::ParseIntError> {
    let a: i32 = "10".parse()?;
    println!("{}", a * 2);
    Ok(())
}
```

```
20
```

`main` returns `Result<(), ParseIntError>`, so `?` propagates a parse failure straight out (the runtime would print it and exit nonzero), and `Ok(())` reports success. Much shorter than the manual `match`.

</details>

**Question #4**

The capstone. Write a program that totals a list of prices given as strings. Define an error enum `PriceError` with variants `NotANumber(String)` (carrying the offending text) and `Negative(f64)`. Write `parse_price(text: &str) -> Result<f64, PriceError>` that parses an `f64` and rejects negatives, and `total(prices: &[&str]) -> Result<f64, PriceError>` that sums them, propagating the first error with `?`. In `main`, total `["1.50", "2.25", "0.75"]` (should succeed) and `["1.50", "oops", "3.00"]` (should fail), printing each result.

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

```rust
#[derive(Debug)]
enum PriceError {
    NotANumber(String),
    Negative(f64),
}

fn parse_price(text: &str) -> Result<f64, PriceError> {
    let value: f64 = text
        .parse()
        .map_err(|_| PriceError::NotANumber(text.to_string()))?;
    if value < 0.0 {
        return Err(PriceError::Negative(value));
    }
    Ok(value)
}

fn total(prices: &[&str]) -> Result<f64, PriceError> {
    let mut sum = 0.0;
    for price in prices {
        sum += parse_price(price)?;
    }
    Ok(sum)
}

fn main() {
    println!("{:?}", total(&["1.50", "2.25", "0.75"]));
    println!("{:?}", total(&["1.50", "oops", "3.00"]));
}
```

```
Ok(4.5)
Err(NotANumber("oops"))
```

Design notes. `parse_price` returns the chapter's error enum, converting a parse failure into `NotANumber` (carrying the bad text via `map_err`, lesson [12.4](https://learnrust.net/chapter-12/handling-invalid-input/)) and rejecting negatives as `Negative`. `total` takes a slice (`&[&str]`, lesson [9.6](https://learnrust.net/chapter-9/slices/)) and uses `?` inside the loop, so the *first* bad price aborts the sum and propagates its error, which is why `"oops"` stops the second total before `"3.00"` is even reached. The success case sums to `4.5`. (Prices here use `f64` for brevity; a real money program counts integer cents, lesson [6.x](https://learnrust.net/chapter-6/chapter-6-summary-and-quiz/).)

If you implemented `Display` for `PriceError` too, you could print `{}` for a clean user message; deriving `Debug` alone is enough for the `{:?}` output shown.

</details>

You can now build programs that fail gracefully: panic only on real bugs, return `Result` for everything a caller might recover from, propagate with `?`, and model errors as the same structs and enums you already know. Chapter 13 turns from *what your code does* to *how it's organized*: modules, visibility, splitting across files, and the `pub` keyword, everything C++ needs headers, header guards, and the preprocessor for, in one chapter.

## Sitemap

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