104 lines
3.3 KiB
Markdown
104 lines
3.3 KiB
Markdown
---
|
|
title: "Handling Errors in Rust"
|
|
date: 2022-04-13T15:31:11-04:00
|
|
toc: false
|
|
images:
|
|
tags:
|
|
- dev
|
|
- rust
|
|
---
|
|
|
|
> This post is day 12 of me taking part in the
|
|
> [#100DaysToOffload](https://100daystooffload.com/) challenge.
|
|
|
|
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),
|
|
}
|
|
```
|