---
title: "12.2 Result and recoverable errors"
description: "Result<T, E> is the enum for operations that can fail; match on it, and understand unwrap and expect as deliberate convert-to-panic choices."
url: "https://learnrust.net/chapter-12/result/"
last_updated: "2026-06-13"
---

# 12.2 Result and recoverable errors

Most failures aren't bugs. A file might not exist; a user might mistype; a number might be too big to parse. The program should handle these and carry on, not panic. Rust's tool for "this operation might fail, and the caller should deal with it" is a type you've been using since chapter 1 without being introduced: `Result`. And like `Option` before it, the reveal is that it's just an enum, so everything from chapter 11 already applies.

## Result is an enum

`Result<T, E>` has exactly two variants:

```rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}
```

`Ok(T)` carries the success value of type `T`; `Err(E)` carries an error value of type `E`. The two type placeholders mean "on success I give you a `T`, on failure I give you an `E`," and a function declares both in its return type. Like `Option`, `Result` is in scope everywhere, so you write `Ok(...)` and `Err(...)` directly.

You've been calling functions that return `Result` all along. `"42".parse::<i32>()` returns `Result<i32, ParseIntError>`: `Ok(42)` when the text is a valid integer, `Err(...)` when it isn't. `read_line` returns a `Result` too. The `.expect("...")` you've tacked onto them since lesson [1.6](https://learnrust.net/chapter-1/reading-input/) was dealing with that `Result`; now you'll learn what it was really doing.

## Matching on a Result

Since `Result` is an enum, you handle it with `match` (lesson [11.2](https://learnrust.net/chapter-11/enums-with-data/)), giving the success and failure cases each their own arm:

```rust
fn main() {
    let text = "42";
    match text.parse::<i32>() {
        Ok(number) => println!("parsed: {}", number * 2),
        Err(error) => println!("not a number: {error}"),
    }
}
```

```
parsed: 84
```

The `Ok(number)` arm binds the parsed `i32` and uses it; the `Err(error)` arm binds the error and reports it. Change `"42"` to `"abc"` and the same code takes the other branch:

```rust
    let text = "abc";
    match text.parse::<i32>() {
        Ok(number) => println!("parsed: {}", number * 2),
        Err(error) => println!("not a number: {error}"),
    }
```

```
not a number: invalid digit found in string
```

No panic, no crash. The program inspected the `Result`, found an `Err`, and responded. That's recoverable error handling: the failure is a value you examine and branch on, not an explosion. And exhaustiveness (lesson [11.1](https://learnrust.net/chapter-11/enums/)) guarantees you can't forget the `Err` case, which is the entire difference from languages where an ignored error silently propagates.

> **Key insight**
>
> `Result` makes failure a *value*, not an interruption. Where other languages throw exceptions that travel invisibly up the stack and may or may not be caught, Rust hands the error back as an ordinary return value of an ordinary enum. You can see in a function's *type* whether it can fail, and the compiler makes you handle the failure before you can use the success. Errors stop being a parallel control-flow system and become regular data.

## unwrap and expect on Result

Writing a full `match` for every fallible call would be heavy, so `Result` has the same shortcuts as `Option` (lesson [11.3](https://learnrust.net/chapter-11/option/)), and now you can see precisely what they do. `unwrap()` returns the `Ok` value or panics with the error inside; `expect("msg")` does the same with your message:

```rust
fn main() {
    let good: i32 = "42".parse().unwrap();    // 42
    println!("{good}");

    let bad: i32 = "abc".parse().unwrap();    // panics
    println!("{bad}");
}
```

```
42

thread 'main' panicked at src/main.rs:5:30:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
```

There it is. `unwrap` on an `Err` panics (lesson [12.1](https://learnrust.net/chapter-12/panic-and-unrecoverable-errors/)), printing the contained error. So every `.expect("failed to read line")` you wrote after `read_line` meant "if reading fails, panic with this message." It was a deliberate choice to treat a recoverable error as unrecoverable, reasonable in a tiny program where there's nothing better to do, but a choice all the same.

A safer cousin, `unwrap_or(default)`, returns the `Ok` value or a fallback without panicking, mirroring `Option`:

```rust
fn main() {
    let n: i32 = "abc".parse().unwrap_or(0);
    println!("{n}");   // 0
}
```

```
0
```

> **Best practice**
>
> `unwrap` and `expect` are fine in examples, prototypes, and tests, and acceptable when an `Err` truly cannot happen and you can say why. In real programs that should keep running, handle the `Err` deliberately: with `match`, with `unwrap_or` and friends, or by passing the error up to a caller who knows what to do. The next lesson's `?` operator makes that last option nearly free.

## Returning a Result of your own

You don't only consume `Result`s; you write functions that produce them. A function that can fail says so in its return type, returning `Ok` on success and `Err` on failure:

```rust
fn parse_percentage(text: &str) -> Result<u8, String> {
    match text.parse::<u8>() {
        Ok(n) if n <= 100 => Ok(n),
        Ok(n) => Err(format!("{n} is over 100")),
        Err(_) => Err(format!("'{text}' is not a number")),
    }
}

fn main() {
    println!("{:?}", parse_percentage("50"));     // Ok(50)
    println!("{:?}", parse_percentage("150"));    // Err("150 is over 100")
    println!("{:?}", parse_percentage("xyz"));    // Err("'xyz' is not a number")
}
```

```
Ok(50)
Err("150 is over 100")
Err("'xyz' is not a number")
```

`parse_percentage` returns `Result<u8, String>`: a `u8` on success, an error message on failure. It validates two ways a percentage can be wrong (not a number, or out of range) and returns a descriptive `Err` for each, using a guard (lesson [11.4](https://learnrust.net/chapter-11/match-in-depth/)) to separate the in-range and out-of-range `Ok` cases. The caller gets a `Result` they must handle, and the function never panics over bad input, it reports it. Using `String` as the error type is the simplest choice; lesson [12.6](https://learnrust.net/chapter-12/custom-error-types/) shows a sturdier one.

## Quiz time

**Question #1**

What are the two variants of `Result<T, E>`, and what does each carry?

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

`Ok(T)` carries the success value of type `T`; `Err(E)` carries the error value of type `E`. A function returning `Result<T, E>` gives back `Ok` with a `T` when it succeeds and `Err` with an `E` when it fails.

</details>

**Question #2**

What does `"abc".parse::<i32>().unwrap()` do, and how is it different from `.unwrap_or(0)`?

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

`.unwrap()` panics, because parsing `"abc"` returns `Err`, and `unwrap` on an `Err` panics with the contained error. `.unwrap_or(0)` instead returns the fallback `0` without panicking. `unwrap` converts a recoverable error into an unrecoverable panic; `unwrap_or` recovers with a default.

</details>

**Question #3**

Write a function `safe_divide(a: i32, b: i32) -> Result<i32, String>` that returns `Ok(a / b)`, or `Err("division by zero")` when `b` is 0. Then handle both cases of `safe_divide(10, 0)` with a `match`.

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

```rust
fn safe_divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("division by zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match safe_divide(10, 0) {
        Ok(result) => println!("result: {result}"),
        Err(message) => println!("error: {message}"),
    }
}
```

```
error: division by zero
```

Returning `Result` lets `safe_divide` report the zero-divisor case as a value the caller handles, instead of panicking on the division (lesson [3.1](https://learnrust.net/chapter-3/syntax-and-semantic-errors/)). The `match` covers both variants, so the error can't be ignored.

</details>

Handling `Result` with `match` everywhere gets repetitive fast, especially when a function calls several fallible operations and wants to pass any failure up to its caller. The next lesson introduces the `?` operator, which collapses that whole pattern into a single character.

## Sitemap

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