---
title: "Handling Multiple Possible Errors in Rust"
date: 2022-08-04
tags:
  - programming
draft: true
---

## Background

Rust handles errors better than any other language I’ve ever used. All possible errors are known at build time, and by design Rust forces you to at a bare minimum *acknowledge* that these errors are possible with `.unwrap()`, otherwise writing specific error handling logic.

### The `Result` enum

All of this is thanks to the `Result` enum, which is defined as follows:

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

`Result` is a generic type that requires two type parameters, `T`, the actual value the result is wrapping, and `E`, the error type in case something goes wrong. In the success case, the `Ok(T)` variant will be used, and in an error case, `Err(E)` will be used.

What makes this powerful is the fact that since `Result` wraps the actual result value, access to that value is prevented unless the possibility of an error is acknowledged.

### Handling `Result`

For the sake of example, say we’re writing a theoretical function, `acquire_banana`, that drives to the store, purchases a banana, and returns the banana.

We have a function called `drive_to_store`. It returns a `Result` that can either be nothing `()`, or a `NavigationError`.

```RS
fn drive_to_store() -> Result<(), NavigationError> { ... }
```

We then have the following function, `acquire_banana`. It also returns a `Result`, but this time it can either be a `Banana` or a `PurchaseError`.

```RS
fn purchase_banana() -> Result<Banana, PurchaseError> { ... }
```

In other languages, we might write the function something like this:

```RS
fn acquire_banana() -> Banana {
    drive_to_store();
    purchase_banana()
}
```

However, this doesn’t work in Rust. There are two issues here. First, while `drive_to_store();` is in theory valid code (it returns an enum that we ignore), `Result`s **must** be handled. Otherwise, you will get compile warnings. Second, we cannot return `purchase_banana` from the function, because its return type is `Result<Banana, PurchaseError>`, not `Banana`.

The naïve solution to this would be using the `unwrap` method on the `Result`s, like this:

```RS
fn acquire_banana() -> Banana {
    drive_to_store().unwrap();
    purchase_banana().unwrap()
}
```

The `.unwrap()` method does exactly what it sounds like. If a `Result` is the `Ok(T)` variant, it returns `T`. However, the naïvety here lies in what happens on the `Err(E)` variant. In that case, it *panics*, printing the error and kills the thread/program. A well-designed program should take errors into account, so this solution is unacceptable.

The best solution is in this case is **error propagation** using the question mark `?` operator. It works similarly to `.unwrap()`, but instead of panicking on the `Err(E)` case, it returns `Err(E)` from the function. This lets you quickly unwrap results without convoluted `match` statements.

This lets you convert this:

```RS
let x = match get_result() {
    Ok(value) => value,
    Err(error) => return Err(error),
};
```

Into this:

```RS
let x = get_result()?;
```
