Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

error-handling: split thiserror into its own slide #2332

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ use-boolean-and = true
"error-handling/converting-error-types-example.html" = "../error-handling/try-conversions.html"
"error-handling/converting-error-types.html" = "../error-handling/try-conversions.html"
"error-handling/deriving-error-enums.html" = "../error-handling/error.html"
"error-handling/dynamic-errors.html" = "../error-handling/thiserror-and-anyhow.html"
"error-handling/error-contexts.html" = "../error-handling/thiserror-and-anyhow.html"
"error-handling/dynamic-errors.html" = "../error-handling/anyhow.html"
"error-handling/error-contexts.html" = "../error-handling/anyhow.html"
"error-handling/thiserror-and-anyhow.html" = "../error-handling/anyhow.html"
"error-handling/panic-unwind.html" = "../error-handling/panics.html"
"error-handling/try-operator.html" = "../error-handling/try.html"
"exercises/concurrency/afternoon.html" = "../../concurrency/async-exercises.html"
Expand Down
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.md)
- [`anyhow`](error-handling/anyhow.md)
- [Exercise: Rewriting with `Result`](error-handling/exercise.md)
- [Solution](error-handling/solution.md)
- [Unsafe Rust](unsafe-rust.md)
Expand Down
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/) crate 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,25 +44,23 @@ 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.
- 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.

# More to Explore

- `anyhow::Error` has support for downcasting, much like `std::any::Any`; the
specific error type stored inside can be extracted for examination if desired
with
[`Error::downcast`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html#method.downcast).

</details>
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