TL;DR:
thiserroris a procedural macro crate that generates the boilerplate for custom error types:impl std::error::Error,impl Display, andimpl From<OtherError>. You write a single#[derive(Error)]enum with#[error("...")]messages;thiserrorhandles everything else. It is the standard choice for library crates that need typed, structured errors. For application code where you just want to propagate errors without defining types, useanyhowinstead.
What Is thiserror?
thiserror lets you define custom Rust error types by annotating enums or structs with #[derive(Error)] and #[error("...")]; eliminating ~50 lines of boilerplate per error type.
# Cargo.toml
[dependencies]
thiserror = "2"use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
#[error("user not found: id={id}")]
UserNotFound { id: u64 },
#[error("invalid input: {message}")]
InvalidInput { message: String },
#[error("unauthorized")]
Unauthorized,
}This generates:
impl std::fmt::Display for AppError(from#[error("...")])impl std::error::Error for AppErrorimpl From<sqlx::Error> for AppError(from#[from])
How Do You Use #[from] to Convert Errors?
#[from] generates a From<T> impl so the ? operator automatically converts upstream errors into your error type.
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ServiceError {
#[error("database error: {0}")]
Db(#[from] sqlx::Error),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
}
async fn get_user_count(pool: &sqlx::PgPool) -> Result<i64, ServiceError> {
let count = sqlx::query_scalar!("SELECT COUNT(*) FROM users")
.fetch_one(pool)
.await?; // sqlx::Error → ServiceError::Db via From
Ok(count.unwrap_or(0))
}The ? operator calls From::from(err) automatically; #[from] makes this work without manual impl blocks.
How Do You Format Error Messages?
The #[error("...")] string supports named fields, positional {0}, and any Display-able expression.
#[derive(Error, Debug)]
pub enum ParseError {
// Named field from struct variant
#[error("expected {expected}, got {got} at line {line}")]
UnexpectedToken { expected: String, got: String, line: usize },
// Positional; tuple variant field 0
#[error("invalid number: {0}")]
InvalidNumber(String),
// Access source error via {0} or named
#[error("config file error: {source}")]
Config { #[from] source: std::io::Error },
// Static message
#[error("end of file reached unexpectedly")]
UnexpectedEof,
}What Is the Difference Between thiserror and anyhow?
thiserror is for defining typed errors (library code). anyhow is for erasing error types into a single dynamic error (application code).
thiserror | anyhow | |
|---|---|---|
| Purpose | Define structured error types | Propagate any error easily |
| Output type | YourError enum/struct | anyhow::Error (type-erased) |
| Pattern matching | ✅ Can match on variants | ❌ Not easily |
| Best for | Libraries, domain errors | Binaries, glue code |
| Boilerplate | Low (derive macro) | Near-zero |
Use thiserror in a library so callers can handle specific error cases. Use anyhow in main.rs or CLI code where you just want to display and exit.
Frequently Asked Questions
Yes. #[derive(Error)] works on both enums and structs. Structs are useful when a module only has one error kind:
#[derive(Error, Debug)]
#[error("connection refused at {address}: {source}")]
pub struct ConnectionError {
address: String,
#[source]
source: std::io::Error,
}#[from] implies #[source] and also generates a From impl. #[source] only marks the field as the error source (for Error::source()) without generating From. Use #[source] when you want to expose the cause but need manual conversion.
thiserror v2 supports no_std with the no-std feature flag. The std::error::Error trait requires nightly in no_std, but thiserror can generate Display and the struct/enum itself without it.
Always use thiserror; the manual approach is more verbose and error-prone. The generated code is identical to what you would write by hand.
Sources
Related Glossary Terms
- anyhow: Companion crate for propagating errors without defining types
- Result: The
Result<T, E>type that error types plug into - Error Handling: Rust's broader error handling philosophy
- Try Operator: The
?operator that#[from]powers - Display/Debug: thiserror generates
Displayoutput that makes custom error messages readable - From / Into:
#[from]derives the conversion impls that integrate custom errors into?
Keep Reading
- Rust Error Handling: Result and Option: thiserror is the standard way to define custom error types
- Learn Rust in 2026: thiserror and anyhow are the first error crates you reach for

