Rust anyhow
anyhow is a crate that gives applications one error type for every failure. anyhow::Error can hold an io error, a parse error, or any other error.
One Result type for any error
Put anyhow = "1" under [dependencies] first. Then anyhow::Result accepts an io error and a parse error through the same ?.
let text = fs::read_to_string(path)?;
let n: i32 = text.trim().parse()?;Attach a note with context
context pins plain words onto a failure: what this step was trying to do. Import the trait with use anyhow::Context.
let n: i32 = text.trim().parse()
.context("score file did not hold a number")?;A lazy note with with_context
When the note needs values from the code, with_context builds it in a closure. And the closure runs only on failure.
let text = fs::read_to_string(path)
.with_context(|| format!("could not open {path}"))?;bail! fails early
bail! is an early return of an Err with your own message, in one line.
if n < 0 {
bail!("score must not be negative, got {n}");
}Print the whole chain with {:#}
The note prints first, then the real cause, joined by a colon. This is the closing program's matcher, run on the bad score file:
match load_score("score.txt") {
Ok(n) => println!("score = {n}"),
Err(e) => println!("failed: {e:#}"),
}Fish the type back out
When code must react to one specific kind, downcast_ref asks the box whether it holds that type.
if let Some(e) = err.downcast_ref::<std::io::Error>() {
println!("io error kind: {:?}", e.kind());
}Example
use anyhow::{Context, Result, bail};
use std::fs;
fn load_score(path: &str) -> Result<i32> {
let text = fs::read_to_string(path)
.with_context(|| format!("could not open {path}"))?;
let n: i32 = text.trim().parse().context("score file did not hold a number")?;
if n < 0 {
bail!("score must not be negative, got {n}");
}
Ok(n)
}
fn main() -> Result<()> {
fs::write("score.txt", "abc").unwrap();
match load_score("score.txt") {
Ok(n) => println!("score = {n}"),
Err(e) => println!("failed: {e:#}"),
}
match load_score("missing.txt") {
Ok(n) => println!("score = {n}"),
Err(e) => println!("failed: {e:#}"),
}
Ok(())
}In this example, load_score returns anyhow::Result, so the io error from read_to_string and the parse error both travel through ? into the same error type — no enum to define. Each step adds a note: with_context names the file it tried to open, context names the step that failed. main writes a bad score file, then asks for a missing file, and prints each error with {:#}. The output shows the chain. Your note comes first. Then comes the real cause: score file did not hold a number: invalid digit found in string.
Note
anyhow::Error is, under the polish, a box around any error — the same idea as Rust Boxed Errors, plus context.
Keep anyhow in applications.
Libraries usually owe their callers precise variants instead.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does .context("...") add to an error?