Error Handling with thiserror & anyhow
Rust's Result and ? operator are the foundation, but two crates make real-world error handling effortless: thiserror for precise library errors, and anyhow for ergonomic application-level reporting.
Learn Error Handling with thiserror & anyhow in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll derive typed error enums with thiserror, propagate and enrich errors with anyhow's context and macros, and learn exactly when to reach for each.
What You'll Learn in This Lesson
1️⃣ Typed Library Errors with thiserror
A library should expose a precise error type its callers can match on. #[derive(Error)] implements std::error::Error , #[error("...")] supplies the Display message, and #[from] lets ? auto-convert a source error.
Because BadNumber carries #[from] , the ? in parse_port turned a ParseIntError into our own typed variant with no manual conversion.
2️⃣ Application Errors with anyhow
In application code you usually just want to propagate and report. anyhow::Result<T> is Result<T, anyhow::Error> , so ? accepts any error, and .context attaches a helpful note as it bubbles up.
Returning anyhow::Result<()> from main printed the full cause chain — your context plus the underlying OS error.
3️⃣ Early Exits with bail! and ensure!
anyhow's macros make guard clauses concise. bail!("msg") returns an error immediately, and ensure!(cond, "msg") bails only when the condition is false — like assert! , but returning instead of panicking.
These keep validation readable: the happy path stays at the top level while invalid inputs exit early with a clear message.
Define a small thiserror enum and return it from a fallible function.
📋 Quick Reference — Errors
Practice quiz
Which crate is designed for defining error types in libraries?
- thiserror
- tokio
- clap
- anyhow
Answer: thiserror. thiserror helps libraries declare precise, typed error enums that callers can match on.
Which crate is aimed at applications that just need easy error propagation?
- serde
- rayon
- anyhow
- thiserror
Answer: anyhow. anyhow gives applications a single, ergonomic error type for reporting, not for callers to match on.
What does thiserror's #[derive(Error)] do?
- Logs errors automatically
- Generates a std::error::Error implementation for your type
- Catches panics
- Spawns a retry task
Answer: Generates a std::error::Error implementation for your type. #[derive(Error)] implements std::error::Error (and helpers) for your custom error enum.
What does the #[error("...")] attribute provide?
- A backtrace flag
- A retry count
- A log level
- The Display message for that variant
Answer: The Display message for that variant. #[error("...")] supplies the Display text for an error variant, with field interpolation like {0} or {path}.
What does #[from] on an error variant's field generate?
- A From conversion so ? can auto-convert that source error
- A Default impl
- A Clone impl
- A Hash impl
Answer: A From conversion so ? can auto-convert that source error. #[from] derives From for the wrapped error so the ? operator converts it into your error type automatically.
What is the type alias anyhow::Result<T> equal to?
- Result<T, Box<str>>
- Result<T, anyhow::Error>
- Option<T>
- Result<T, String>
Answer: Result<T, anyhow::Error>. anyhow::Result<T> is shorthand for Result<T, anyhow::Error>, a dynamic error that wraps almost anything.
What does the .context("...") method from anyhow add?
- A timeout
- A mutex guard
- A new thread
- Extra descriptive context to an error as it propagates
Answer: Extra descriptive context to an error as it propagates. .context(...) attaches a human-readable message, building a helpful chain as the error bubbles up.
What does anyhow's bail!("msg") macro do?
- Retries the operation
- Logs and continues
- Returns early with an error built from the message
- Panics the thread
Answer: Returns early with an error built from the message. bail! is shorthand for returning Err(anyhow!(...)) early from the current function.
Compared to Box<dyn Error>, what does anyhow::Error add?
- Compile-time variant checking
- Captured backtraces and easy .context, plus single-word ergonomics
- Zero-cost typed matching
- Automatic logging to a file
Answer: Captured backtraces and easy .context, plus single-word ergonomics. anyhow::Error wraps any error like Box<dyn Error> but adds context chaining and backtrace capture.
What is the recommended split between the two crates?
- thiserror for library error types, anyhow for application-level handling
- Never use either
- thiserror everywhere
- anyhow everywhere
Answer: thiserror for library error types, anyhow for application-level handling. Libraries expose typed errors with thiserror; applications consume and report them ergonomically with anyhow.