#+TITLE: Handling Multiple Possible Errors in Rust
#+DATE: 2022-08-04
#+FILETAGS: programming
#+HUGO_DRAFT: t

* Background
:PROPERTIES:
:CUSTOM_ID: background
:END:
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
:PROPERTIES:
:CUSTOM_ID: the-result-enum
:END:
All of this is thanks to the =Result= enum, which is defined as follows:

#+begin_src rust
enum Result<T, E> {
   Ok(T),
   Err(E),
}
#+end_src

=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=
:PROPERTIES:
:CUSTOM_ID: handling-result
:END:
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=.

#+begin_src rust
fn drive_to_store() -> Result<(), NavigationError> { ... }
#+end_src

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=.

#+begin_src rust
fn purchase_banana() -> Result<Banana, PurchaseError> { ... }
#+end_src

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

#+begin_src rust
fn acquire_banana() -> Banana {
    drive_to_store();
    purchase_banana()
}
#+end_src

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:

#+begin_src rust
fn acquire_banana() -> Banana {
    drive_to_store().unwrap();
    purchase_banana().unwrap()
}
#+end_src

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:

#+begin_src rust
let x = match get_result() {
    Ok(value) => value,
    Err(error) => return Err(error),
};
#+end_src

Into this:

#+begin_src rust
let x = get_result()?;
#+end_src
