From 2b837a207d3fa9b3b3734b025c9f43505dc53de4 Mon Sep 17 00:00:00 2001 From: Kaan Genc Date: Wed, 13 Apr 2022 21:56:07 -0400 Subject: [PATCH] handling errors in rust --- .../2022.04.13.handling-errors-in-rust.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 content/posts/2022.04.13.handling-errors-in-rust.md diff --git a/content/posts/2022.04.13.handling-errors-in-rust.md b/content/posts/2022.04.13.handling-errors-in-rust.md new file mode 100644 index 0000000..4a75ea9 --- /dev/null +++ b/content/posts/2022.04.13.handling-errors-in-rust.md @@ -0,0 +1,100 @@ +--- +title: "Handling Errors in Rust" +date: 2022-04-13T15:31:11-04:00 +toc: false +images: +tags: + - dev + - rust +--- + +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; +``` + +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), +} +```