From aeb643f380caaefa9dc3e14f3831550e8b863898 Mon Sep 17 00:00:00 2001 From: Frances Wingerter <91758128+fw-immunant@users.noreply.github.com> Date: Fri, 20 Sep 2024 20:56:22 +0000 Subject: [PATCH] error-handling: split thiserror into its own slide (#2332) `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. Fixes #2027. --- book.toml | 5 +- src/SUMMARY.md | 3 +- .../{thiserror-and-anyhow.md => anyhow.md} | 38 +++++++------- src/error-handling/thiserror.md | 51 +++++++++++++++++++ src/error-handling/try-conversions.md | 2 +- 5 files changed, 74 insertions(+), 25 deletions(-) rename src/error-handling/{thiserror-and-anyhow.md => anyhow.md} (58%) create mode 100644 src/error-handling/thiserror.md diff --git a/book.toml b/book.toml index 2196e223..95a71e80 100644 --- a/book.toml +++ b/book.toml @@ -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" diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f7feb187..ff7b6239 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/error-handling/thiserror-and-anyhow.md b/src/error-handling/anyhow.md similarity index 58% rename from src/error-handling/thiserror-and-anyhow.md rename to src/error-handling/anyhow.md index 2ce57060..10992257 100644 --- a/src/error-handling/thiserror-and-anyhow.md +++ b/src/error-handling/anyhow.md @@ -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`. -- `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}; @@ -46,25 +44,23 @@ fn main() {
-## `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`. 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` is a type alias for `Result`. -- Actual error type inside of it can be extracted for examination if necessary. -- Functionality provided by `anyhow::Result` 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` 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). +
diff --git a/src/error-handling/thiserror.md b/src/error-handling/thiserror.md new file mode 100644 index 00000000..aa5925c6 --- /dev/null +++ b/src/error-handling/thiserror.md @@ -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`, `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 { + 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:?}"), + } +} +``` + +
+ +- 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. + +
diff --git a/src/error-handling/try-conversions.md b/src/error-handling/try-conversions.md index 54fbf9f3..6a245bc3 100644 --- a/src/error-handling/try-conversions.md +++ b/src/error-handling/try-conversions.md @@ -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}"), } }