bgenc.net/content/posts/2022.04.13.handling-errors-in-rust.md

104 lines
3.3 KiB
Markdown
Raw Normal View History

2022-04-13 20:56:07 -05:00
---
title: "Handling Errors in Rust"
date: 2022-04-13T15:31:11-04:00
toc: false
images:
tags:
- dev
- rust
---
2022-04-13 20:58:32 -05:00
> This post is day 12 of me taking part in the
> [#100DaysToOffload](https://100daystooffload.com/) challenge.
2022-04-13 20:56:07 -05:00
Rust uses a pretty interesting way to deal with errors. Languages like Python
and JavaScript allow you to throw errors completely unchecked. C does error
handling through the `errno` which is also completely unchecked and not enforced
in any way by the programming language. This means any function you call may
throw an error, and other than documentation there's nothing to let you know
that it might do so.
Worse, most of these languages don't tell you what kind of error a function
might throw. That's obvious in JavaScript, and TypeScript doesn't help with it
either (errors caught have `any` or `unknown` type). C++ and Python let you
catch specific types of errors, but you have to rely on documentation to know
which types those are.
```typescript
try {
// ...
} catch (err: any) {
// Is it a file error? Did I access something undefined? Who knows.
```
Java, on the other hand, requires you to explicitly mark what errors a function
can throw and enforces that you handle all the errors or explicitly mark that
you are propagating it.
```java
public class Main {
static void example() throws ArithmeticException;
}
```
Rust is a lot closer to this as it enforces that you handle errors. The main
difference is that instead of using a special syntax for error handling, it's
built into the return type of the function directly. Which is why you'll see
functions like this:
```rust
fn example() -> Result<String, io::Error>;
```
This sometimes makes error handling a little harder, but luckily we have many
tools that can help us handle errors.
## When you don't care about the error
Sometimes you just don't care about the error. Perhaps the error is impossible
to recover from and the best you can do is print an error message and exit, or
how you handle it is the same regardless of what the error is.
### To just exit
Two great options in this case is [die](https://crates.io/crates/die) and
[tracing-unwrap](https://crates.io/crates/tracing-unwrap). Both of these options
allow you to unwrap a `Result` type, and print a message if it's an `Error` and
exit. `die` allows you to pick the error code to exit with, while
`tracing-unwrap` uses the [tracing](https://crates.io/crates/tracing) logging
framework. You can also always use the built-in [unwrap or expect](https://learning-rust.github.io/docs/e4.unwrap_and_expect.html) functions.
```rust
// die
let output = example().die_code("some error happened", 12);
// tracing-unwrap
let output = example().unwrap_or_log()
```
If you are writing a function that might return any type of error, then
[anyhow](https://crates.io/crates/anyhow) is your best option.
```rust
fn main() -> anyhow::Result<()> {
let output = example()?;
}
```
## When you do care about the error
If you do care about what type of error you return, then you need
[thiserror](https://crates.io/crates/thiserror). `thiserror` allows you to write
your own error types and propagate errors.
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ExampleError {
#[error("The error message for this type")]
Simple(String),
#[error("An error that you are propagating")]
FileError(#[from] io::Error),
}
```