Skip to content

Commit aeb643f

Browse files
authored
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.
1 parent db0b161 commit aeb643f

File tree

5 files changed

+74
-25
lines changed

5 files changed

+74
-25
lines changed

book.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@ use-boolean-and = true
138138
"error-handling/converting-error-types-example.html" = "../error-handling/try-conversions.html"
139139
"error-handling/converting-error-types.html" = "../error-handling/try-conversions.html"
140140
"error-handling/deriving-error-enums.html" = "../error-handling/error.html"
141-
"error-handling/dynamic-errors.html" = "../error-handling/thiserror-and-anyhow.html"
142-
"error-handling/error-contexts.html" = "../error-handling/thiserror-and-anyhow.html"
141+
"error-handling/dynamic-errors.html" = "../error-handling/anyhow.html"
142+
"error-handling/error-contexts.html" = "../error-handling/anyhow.html"
143+
"error-handling/thiserror-and-anyhow.html" = "../error-handling/anyhow.html"
143144
"error-handling/panic-unwind.html" = "../error-handling/panics.html"
144145
"error-handling/try-operator.html" = "../error-handling/try.html"
145146
"exercises/concurrency/afternoon.html" = "../../concurrency/async-exercises.html"

src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@
197197
- [Try Operator](error-handling/try.md)
198198
- [Try Conversions](error-handling/try-conversions.md)
199199
- [`Error` Trait](error-handling/error.md)
200-
- [`thiserror` and `anyhow`](error-handling/thiserror-and-anyhow.md)
200+
- [`thiserror`](error-handling/thiserror.md)
201+
- [`anyhow`](error-handling/anyhow.md)
201202
- [Exercise: Rewriting with `Result`](error-handling/exercise.md)
202203
- [Solution](error-handling/solution.md)
203204
- [Unsafe Rust](unsafe-rust.md)

src/error-handling/thiserror-and-anyhow.md renamed to src/error-handling/anyhow.md

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@
22
minutes: 5
33
---
44

5-
# `thiserror` and `anyhow`
5+
# `anyhow`
66

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

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

1614
```rust,editable,compile_fail
1715
use anyhow::{bail, Context, Result};
@@ -46,25 +44,23 @@ fn main() {
4644

4745
<details>
4846

49-
## `thiserror`
50-
51-
- The `Error` derive macro is provided by `thiserror`, and has lots of useful
52-
attributes to help define error types in a compact way.
53-
- The `std::error::Error` trait is derived automatically.
54-
- The message from `#[error]` is used to derive the `Display` trait.
55-
56-
## `anyhow`
57-
5847
- `anyhow::Error` is essentially a wrapper around `Box<dyn Error>`. As such it's
5948
again generally not a good choice for the public API of a library, but is
6049
widely used in applications.
6150
- `anyhow::Result<V>` is a type alias for `Result<V, anyhow::Error>`.
62-
- Actual error type inside of it can be extracted for examination if necessary.
63-
- Functionality provided by `anyhow::Result<T>` may be familiar to Go
64-
developers, as it provides similar usage patterns and ergonomics to
65-
`(T, error)` from Go.
51+
- Functionality provided by `anyhow::Error` may be familiar to Go developers, as
52+
it provides similar behavior to the Go `error` type and
53+
`Result<T, anyhow::Error>` is much like a Go `(T, error)` (with the convention
54+
that only one element of the pair is meaningful).
6655
- `anyhow::Context` is a trait implemented for the standard `Result` and
6756
`Option` types. `use anyhow::Context` is necessary to enable `.context()` and
6857
`.with_context()` on those types.
6958

59+
# More to Explore
60+
61+
- `anyhow::Error` has support for downcasting, much like `std::any::Any`; the
62+
specific error type stored inside can be extracted for examination if desired
63+
with
64+
[`Error::downcast`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html#method.downcast).
65+
7066
</details>

src/error-handling/thiserror.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
minutes: 5
3+
---
4+
5+
# `thiserror`
6+
7+
The [`thiserror`](https://docs.rs/thiserror/) crate provides macros to help
8+
avoid boilerplate when defining error types. It provides derive macros that
9+
assist in implementing `From<T>`, `Display`, and the `Error` trait.
10+
11+
```rust,editable,compile_fail
12+
use std::fs;
13+
use std::io::Read;
14+
use thiserror::Error;
15+
16+
#[derive(Error)]
17+
enum ReadUsernameError {
18+
#[error("I/O error: {0}")]
19+
IoError(#[from] io::Error),
20+
#[error("Found no username in {0}")]
21+
EmptyUsername(String),
22+
}
23+
24+
fn read_username(path: &str) -> Result<String, ReadUsernameError> {
25+
let mut username = String::with_capacity(100);
26+
File::open(path)?.read_to_string(&mut username)?;
27+
if username.is_empty() {
28+
return Err(ReadUsernameError::EmptyUsername(String::from(path)));
29+
}
30+
Ok(username)
31+
}
32+
33+
fn main() {
34+
//fs::write("config.dat", "").unwrap();
35+
match read_username("config.dat") {
36+
Ok(username) => println!("Username: {username}"),
37+
Err(err) => println!("Error: {err:?}"),
38+
}
39+
}
40+
```
41+
42+
<details>
43+
44+
- The `Error` derive macro is provided by `thiserror`, and has lots of useful
45+
attributes to help define error types in a compact way.
46+
- The message from `#[error]` is used to derive the `Display` trait.
47+
- Note that the (`thiserror::`)`Error` derive macro, while it has the effect of
48+
implementing the (`std::error::`)`Error` trait, is not the same this; traits
49+
and macros do not share a namespace.
50+
51+
</details>

src/error-handling/try-conversions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl Error for ReadUsernameError {}
4343
impl Display for ReadUsernameError {
4444
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
4545
match self {
46-
Self::IoError(e) => write!(f, "IO error: {e}"),
46+
Self::IoError(e) => write!(f, "I/O error: {e}"),
4747
Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
4848
}
4949
}

0 commit comments

Comments
 (0)