Error Reporting
Present actionable errors to CLI users with anyhow and miette.
Search across all documentation pages
Present actionable errors to CLI users with anyhow and miette.
use anyhow::Context;
fn read_config(path: &str) -> anyhow::Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("failed to read config at {path}"))
}
fn main() -> anyhow::Result<()> {
let cfg = read_config("app.toml")?;
println!("loaded {cfg}");
Ok(())
}When to reach for this: Application binaries where users need context-rich messages, not library-style typed errors.
use miette::{miette, IntoDiagnostic, Result};
#[derive(Debug, thiserror::Error, Diagnostic)]
#[error("invalid port: {0}")]
#[diagnostic(code(app::invalid_port))]
struct InvalidPort(u16);
fn parse_port(s: &str) -> Result<u16> {
let port: u16 = s.parse().into_diagnostic()?;
if port == 0 {
return Err(miette!(InvalidPort(port)));
}
Ok(port)
}
fn main() -> Result<()> {
let port = parse_port("0")?;
println!("listening on {port}");
Ok(())
}What this demonstrates:
anyhow::Context adds location context to std errorsmiette provides diagnostic codes and formatted reportsthiserror defines small domain error typesmain returns Result for automatic non-zero exit| Crate | Role |
|---|---|
anyhow | Ergonomic Result in binaries |
thiserror | Typed errors in libraries |
miette | Beautiful reports with source spans |
use std::process::ExitCode;
fn main() -> ExitCode {
if let Err(e) = run() {
eprintln!("{e:#}");
return ExitCode::FAILURE;
}
ExitCode::SUCCESS
}Use {:#} for the full anyhow chain.
Display only - Users miss the error chain. Fix: Use {:#} or miette::Report.Result and reserve panics for bugs.thiserror; binaries wrap with anyhow.--verbose or env hint./home/ci/... confuses users. Fix: Strip or relativize paths in user messages.| Alternative | Use When | Don't Use When |
|---|---|---|
thiserror only | Libraries with matchable errors | Top-level binary needing context chains |
Plain eprintln! | Tiny scripts | Multi-step pipelines needing context |
color-eyre | Dev tools during development | Minimal dependency production CLIs |
Avoid it. Library consumers need typed errors. Use thiserror and let binaries wrap with anyhow.
Use miette::Diagnostic with a code(...) attribute for searchable error IDs.
Define an ErrorResponse { code, message, details } struct and print serde JSON on failure when --json is set.
Warnings go to stderr with WARN: prefix. Errors use non-zero exit and distinct formatting.
Use fluent or rust-i18n for translated messages. Keep error codes stable across locales.
Assert on err.to_string() or format!("{err:#}") in integration tests.
Log full detail with tracing::error!. Print a short user-facing summary to stderr.
Collect errors in a Vec and print all before exiting. Common in linters and validators.
anyhow supports .context() and #[source]. Each layer adds operator-relevant detail.
miette works on stable Rust. Enable fancy feature for graphical reports when stderr is a TTY.
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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026