Add old edits on draft from August 4

main
Elnu 2 years ago
parent e567e49c46
commit 6f0fa900c3

@ -0,0 +1,81 @@
---
title: "Handling Multiple Possible Errors in Rust"
date: 2022-08-04
tags:
- programming
draft: true
---
## Background
Rust handles errors better than any other language Ive 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 were 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 doesnt 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()?;
```
Loading…
Cancel
Save