Rust anyhow Patterns
anyhow patterns are the deeper tools for working with anyhow::Error in a real application. ensure! guards a rule in one line.
ensure! guards a rule
One line replaces an if, a message, and an early return.
ensure!(n <= 99, "level {n} is above the maximum of 99");anyhow! builds an error value
When you need the error as a value, not an early return, anyhow! builds it from a message.
let err = anyhow!("level file is empty");Walk every layer with chain()
chain() yields the top error first, then each cause under it, in order.
for cause in e.chain() {
println!("cause: {cause}");
}Find the root cause
chain() and root_cause() look through every layer of context at once.
if e.root_cause().downcast_ref::<std::num::ParseIntError>().is_some() {
println!("(the root cause is a parse failure)");
}The same error, two printings
{:#} gives one line for logs. {:?} gives the numbered report for humans.
println!("one line: {e:#}");
println!("full report:\n{e:?}");Hand the typed error back
At a boundary, downcast by value gives the original typed error to code that matches on it.
if let Ok(io_err) = e.downcast::<std::io::Error>() {
println!("kind: {:?}", io_err.kind());
}Example
use anyhow::{ensure, Context, Result};
use std::fs;
fn load_level(path: &str) -> Result<u32> {
let text = fs::read_to_string(path)
.with_context(|| format!("could not open {path}"))?;
let n: u32 = text.trim().parse().context("level file did not hold a number")?;
ensure!(n <= 99, "level {n} is above the maximum of 99");
Ok(n)
}
fn run() -> Result<u32> {
load_level("level.txt").context("could not load the starting level")
}
fn report(e: anyhow::Error) {
if e.root_cause().downcast_ref::<std::num::ParseIntError>().is_some() {
println!("(the root cause is a parse failure)");
}
println!("one line: {e:#}");
println!("full report:\n{e:?}");
}
fn main() {
fs::write("level.txt", "abc").unwrap();
report(run().unwrap_err());
fs::write("level.txt", "250").unwrap();
println!("too big: {:#}", run().unwrap_err());
}Key points
- Each .context() call adds one more layer on top.
- The error remembers the whole stack, newest first.
In this example, the level file holds abc, so parsing fails deep inside load_level. report first digs through both context layers with root_cause() and recognises the ParseIntError at the bottom. Then it prints the same error twice. With {:#} you get one line: your newest note, the older note, then the real cause. With {:?} you get the full report: the top note, then every cause numbered under Caused by:. Finally the file holds 250: it parses, but ensure! rejects it, and its message appears at the end of the chain.
Note
Downcasting is an escape hatch for boundaries.
If most of your code downcasts, the design wants typed errors instead — the next two lessons show the alternatives.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
You must react only when the deepest cause of an anyhow error is a ParseIntError. What reaches it?