TL;DR:
anyhowis the simplest way to do application-level error propagation in Rust in 2026. Use it in binaries, CLIs, and server glue code when you want?, context, and readable top-level errors without designing a custom error enum for every layer. Do not useanyhowas your public library error type when callers need to match on structured variants.
What Is anyhow?
anyhow erases concrete error types into a single anyhow::Error, and it is the default ergonomic choice for application-layer Rust error handling in 2026.
# Cargo.toml
[dependencies]
anyhow = "1"use anyhow::Result;
fn read_config(path: &str) -> Result<String> {
let content = std::fs::read_to_string(path)?; // std::io::Error → anyhow::Error
let trimmed = content.trim().to_string();
Ok(trimmed)
}
fn main() -> Result<()> {
let config = read_config("config.toml")?;
println!("Config: {config}");
Ok(())
}No error enum needed. No impl From. The ? operator converts any std::error::Error into anyhow::Error.
This page matters most for CLI authors, backend engineers, and Rust learners trying to understand when anyhow is a shortcut versus when it becomes the wrong abstraction.
When Should You Use anyhow?
Use anyhow when the main job of your code is to propagate errors upward cleanly, not to expose a typed error contract to downstream callers.
anyhow is a strong fit when:
- you are writing a binary crate, CLI, job runner, or service entrypoint
- you want fast progress without custom error boilerplate
- top-level logging and operator readability matter more than variant matching
- lower layers already return many different error types
It is a weaker fit when you are designing a library API, domain model, or SDK where callers need stable, matchable error variants.
How Do You Add Context to Errors?
.context("...") wraps an error with an explanatory message; the original error is preserved as the source.
use anyhow::{Context, Result};
fn load_user(id: u64) -> Result<User> {
let data = std::fs::read_to_string("users.json")
.context("failed to read users.json")?;
let user: User = serde_json::from_str(&data)
.with_context(|| format!("failed to parse user with id={id}"))?;
Ok(user)
}When this error is displayed, the output chains all context messages:
failed to parse user with id=42: expected `,` or `}` at line 5 column 3Use .context("static message") for fixed strings and .with_context(|| format!(...)) for dynamic messages (the closure avoids allocation when there is no error).
How Do You Create Errors From Scratch?
Use anyhow!("...") for ad-hoc errors and bail!("...") to return an error immediately.
use anyhow::{anyhow, bail, Result};
fn validate_age(age: i32) -> Result<()> {
if age < 0 {
bail!("age cannot be negative, got {age}"); // returns Err immediately
}
if age > 150 {
return Err(anyhow!("age {age} is unrealistically large"));
}
Ok(())
}bail!("msg") is equivalent to return Err(anyhow!("msg")); a convenience for early returns.
anyhow vs thiserror in 2026
Choose anyhow for application-layer error propagation in 2026; choose thiserror when you need structured, public, typed errors.
anyhow | thiserror | |
|---|---|---|
| Purpose | Propagate any error | Define typed errors |
| Error type | anyhow::Error (dynamic) | Your custom enum/struct |
| Pattern matching | ❌ Not ergonomic | ✅ Full match support |
? operator | Works with any error | Works after From impls |
| Best for | Binaries, CLI, application glue | Libraries, domain models |
| Downcast | err.downcast_ref::<SomeError>() | Not needed |
A common pattern is still the best one in 2026: define domain errors with thiserror in library crates, then use anyhow in the binary crate that calls them.
How Do You Downcast an anyhow::Error?
anyhow::Error preserves the original error type and supports downcasting when you need to inspect it.
use anyhow::Result;
fn handle(err: anyhow::Error) {
if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
eprintln!("IO error: kind={:?}", io_err.kind());
} else {
eprintln!("Other error: {err}");
}
}Downcasting is an escape hatch; if you find yourself doing it often, consider thiserror for that error path instead.
Why Does anyhow Matter Professionally?
anyhow matters because production Rust work is not only about perfect type modeling; it is also about choosing when not to over-engineer error plumbing.
Strong Rust engineers know both sides of the tradeoff. They know when typed errors are the right API design, and they know when a binary or service layer simply needs fast, readable, contextual error propagation. anyhow is one of the clearest examples of Rust maturity being about judgment, not only about using the most explicit type at every layer.
Frequently Asked Questions
You can, but it is not recommended. Library callers receive anyhow::Error and cannot match on specific error cases without downcasting. Use thiserror to expose typed errors to your callers. Reserve anyhow for the top-level application layer.
Use {:#} (alternate display) or the chain() iterator:
eprintln!("Error: {:#}", err); // prints all context layers
for cause in err.chain() {
eprintln!(" caused by: {cause}");
}anyhow::Error is one pointer (8 bytes on 64-bit). It box-allocates the inner error; there is a heap allocation per error value. In the happy path (no error), there is zero overhead. For error-heavy hot paths, typed errors avoid the allocation.
anyhow::Result<T> is a type alias for Result<T, anyhow::Error>. It is a convenience; you can still use std::result::Result<T, anyhow::Error> directly.
Sources
Related Glossary Terms
- thiserror: Define typed errors that
anyhowcan wrap - Result: The
Result<T, E>typeanyhowplugs into - Try Operator: The
?operator central toanyhowusage - Error Handling: Rust's broader error handling philosophy
Keep Reading
- Rust Error Handling: Result and Option: the foundation anyhow builds on
- Learn Rust in 2026: where anyhow fits in a real Rust project
