anyhow & thiserror
thiserror defines typed errors for libraries. anyhow provides ergonomic Result handling for applications with context chains and downcasting.
Recipe
# Library crate
thiserror = "2"
# Binary / service crate
anyhow = "1"
thiserror = "2" # if you expose typed errors internally// library
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("missing key: {0}")]
Missing(&'static str),
#[error(transparent)]
Io(#[from] std::io::Error),
}// binary
use anyhow::{Context, Result};
fn load() -> Result<String> {
std::fs::read_to_string("config.toml")
.context("reading config.toml")?;
Ok(String::new())
}When to reach for this:
- Libraries:
thiserrorenums with#[from] - CLIs and services:
anyhow::Resultinmainand top-level handlers
Working Example
use anyhow::{Context, Result};
use thiserror::Error;
#[derive(Debug, Error)]
enum ParseError {
#[error("invalid port: {0}")]
BadPort(u16),
}
fn parse_port(s: &str) -> Result<u16, ParseError> {
let n: u16 = s.parse().map_err(|_| ParseError::BadPort(0))?;
if n == 0 {
return Err(ParseError::BadPort(n));
}
Ok(n)
}
fn main() -> Result<()> {
let port = parse_port("8080").context("parsing PORT env")?;
println!("{port}");
Ok(())
}What this demonstrates:
- Library boundary returns typed
ParseError - Application adds context with
.context() mainreturnsanyhow::Result<()>
Deep Dive
Split by Crate Type
| Crate kind | Error type | Why |
|---|---|---|
| Library | thiserror enum | Callers match variants |
| Binary | anyhow::Error | One propagation path to main |
| Workspace | pub type AppError = anyhow::Error in bin only | Keep lib surface typed |
Context Chains
use anyhow::Context;
db_query().context("loading users for billing run")?;Printed backtrace shows the full chain when RUST_BACKTRACE=1.
Gotchas
- anyhow in public library APIs - callers cannot match errors. Fix: export
thiserrortypes from lib crates. - Stringly
map_err(|e| e.to_string())- loses source. Fix:#[from]or.context(). - Too many error enums - fragmentation. Fix: one enum per crate module with
#[from]. - Panicking in
Fromimpls - hides bugs. Fix: only?on realFromconversions. - Mixing
Box<dyn Error>- outdated pattern in new code. Fix: standardize on anyhow/thiserror.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Manual impl Error | no_std embedded | Normal std services |
snafu | Builder-style errors | Team already on thiserror |
eyre | Colorized reports in CLI | Libraries |
FAQs
Can libraries use anyhow internally?
Yes in private modules, but public functions should return typed errors.
How do I add context in async?
Same .context() on ? - works across await points.
thiserror 1 vs 2?
Prefer 2 for new projects. Check workspace alignment before upgrading.
Should HTTP handlers use anyhow?
Map to status codes at the boundary; use typed domain errors inside handlers.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.