Skip to content

Commit

Permalink
error-handling: split thiserror into its own slide
Browse files Browse the repository at this point in the history
`thiserror` is best understood as a way to eliminate boilerplate on
the patterns we've already seen, and then we can show it in
conjunction with `anyhow` subsequently.
  • Loading branch information
fw-immunant committed Aug 30, 2024
1 parent 3269cb9 commit a1b377d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@
- [Try Operator](error-handling/try.md)
- [Try Conversions](error-handling/try-conversions.md)
- [`Error` Trait](error-handling/error.md)
- [`thiserror` and `anyhow`](error-handling/thiserror-and-anyhow.md)
- [`thiserror`](error-handling/thiserror-and-anyhow.md)
- [`anyhow`](error-handling/thiserror-and-anyhow.md)
- [Exercise: Rewriting with `Result`](error-handling/exercise.md)
- [Solution](error-handling/solution.md)
- [Unsafe Rust](unsafe-rust.md)
Expand Down
33 changes: 12 additions & 21 deletions src/error-handling/thiserror-and-anyhow.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
minutes: 5
---

# `thiserror` and `anyhow`
# `anyhow`

The [`thiserror`](https://docs.rs/thiserror/) and
[`anyhow`](https://docs.rs/anyhow/) crates are widely used to simplify error
handling.
The [`anyhow`](https://docs.rs/anyhow/) crates provides a rich error type with
support for carrying additional contextual information, which can be used to
provide a semantic trace of what the program was doing leading up to the error.

- `thiserror` is often used in libraries to create custom error types that
implement `From<T>`.
- `anyhow` is often used by applications to help with error handling in
functions, including adding contextual information to your errors.
This can be combined with the convenience macros from `thiserror` to avoid
writing out trait impls explicitly for custom error types.

```rust,editable,compile_fail
use anyhow::{bail, Context, Result};
Expand Down Expand Up @@ -46,23 +44,16 @@ fn main() {

<details>

## `thiserror`

- The `Error` derive macro is provided by `thiserror`, and has lots of useful
attributes to help define error types in a compact way.
- The `std::error::Error` trait is derived automatically.
- The message from `#[error]` is used to derive the `Display` trait.

## `anyhow`

- `anyhow::Error` is essentially a wrapper around `Box<dyn Error>`. As such it's
again generally not a good choice for the public API of a library, but is
widely used in applications.
- `anyhow::Result<V>` is a type alias for `Result<V, anyhow::Error>`.
- Actual error type inside of it can be extracted for examination if necessary.
- Functionality provided by `anyhow::Result<T>` may be familiar to Go
developers, as it provides similar usage patterns and ergonomics to
`(T, error)` from Go.
- Actual error type inside of it can be extracted for examination if necessary,
much like `std::any::Any`.
- Functionality provided by `anyhow::Error` may be familiar to Go developers, as
it provides similar behavior to the Go `error` type and
`Result<T, anyhow::Error>` is much like a Go `(T, error)` (with the convention
that only one element of the pair is meaningful).
- `anyhow::Context` is a trait implemented for the standard `Result` and
`Option` types. `use anyhow::Context` is necessary to enable `.context()` and
`.with_context()` on those types.
Expand Down
51 changes: 51 additions & 0 deletions src/error-handling/thiserror.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
minutes: 5
---

# `thiserror`

The [`thiserror`](https://docs.rs/thiserror/) crate provides macros to help
avoid boilerplate when defining error types. It provides derive macros that
assist in implementing `From<T>`, `Display`, and the `Error` trait.

```rust,editable,compile_fail
use std::fs;
use std::io::Read;
use thiserror::Error;
#[derive(Error)]
enum ReadUsernameError {
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("Found no username in {0}")]
EmptyUsername(String),
}
fn read_username(path: &str) -> Result<String, ReadUsernameError> {
let mut username = String::with_capacity(100);
File::open(path)?.read_to_string(&mut username)?;
if username.is_empty() {
return Err(ReadUsernameError::EmptyUsername(String::from(path)));
}
Ok(username)
}
fn main() {
//fs::write("config.dat", "").unwrap();
match read_username("config.dat") {
Ok(username) => println!("Username: {username}"),
Err(err) => println!("Error: {err:?}"),
}
}
```

<details>

- The `Error` derive macro is provided by `thiserror`, and has lots of useful
attributes to help define error types in a compact way.
- The message from `#[error]` is used to derive the `Display` trait.
- Note that the (`thiserror::`)`Error` derive macro, while it has the effect of
implementing the (`std::error::`)`Error` trait, is not the same this; traits
and macros do not share a namespace.

</details>
2 changes: 1 addition & 1 deletion src/error-handling/try-conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl Error for ReadUsernameError {}
impl Display for ReadUsernameError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::IoError(e) => write!(f, "IO error: {e}"),
Self::IoError(e) => write!(f, "I/O error: {e}"),
Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
}
}
Expand Down

0 comments on commit a1b377d

Please sign in to comment.