Error Crates (anyhow / thiserror)
Rusts error story is structural: Result
Result, ?, thiserror, anyhow, and the From trick
EXAMPLE
// Cargo.toml
// [dependencies]
// thiserror = "1"
// anyhow = "1"
use std::fs;
use std::io;
use thiserror::Error;
use anyhow::{Context, Result};
// ============================================================
// 1) Custom error type with thiserror — for LIBRARY code
// ============================================================
#[derive(Debug, Error)]
pub enum OrderError {
#[error("order {0} not found")]
NotFound(String),
#[error("order {id} cannot move from {from} to {to}")]
BadTransition { id: String, from: String, to: String },
#[error("database error")]
Db(#[from] sqlx::Error), // From impl is generated automatically
#[error("io error")]
Io(#[from] io::Error),
}
// ============================================================
// 2) ? operator propagates errors and converts via From
// ============================================================
pub fn load_order_text(path: &str) -> Result<String, OrderError> {
let raw = fs::read_to_string(path)?; // io::Error -> OrderError via From
if raw.is_empty() {
return Err(OrderError::NotFound(path.into()));
}
Ok(raw)
}
// ============================================================
// 3) match with rich pattern fields
// ============================================================
fn explain(err: &OrderError) {
match err {
OrderError::NotFound(id) => println!("missing: {id}"),
OrderError::BadTransition { id, from, to } =>
println!("bad transition for {id}: {from} -> {to}"),
OrderError::Db(e) => println!("db: {e}"),
OrderError::Io(e) => println!("io: {e}"),
}
}
// ============================================================
// 4) anyhow::Result for APPLICATION code — lossy but ergonomic
// ============================================================
fn main() -> Result<()> {
let raw = fs::read_to_string("/etc/example/config.toml")
.context("reading config")?; // attach context to the error chain
let cfg: Config = toml::from_str(&raw)
.context("parsing config TOML")?;
serve(&cfg).context("running server")?;
Ok(())
}
// ============================================================
// 5) The error chain is printed top-down by default formatters
// e.g. "running server: parsing config TOML: invalid TOML at line 8"
// ============================================================
// ============================================================
// 6) Boxing a heterogeneous error type when you cannot enumerate
// ============================================================
fn parse_any(s: &str) -> std::result::Result<u64, Box<dyn std::error::Error + Send + Sync>> {
let n: u64 = s.trim().parse()?;
Ok(n)
}
// ============================================================
// 7) Convert with map_err when From does not apply
// ============================================================
fn read_or_default(path: &str) -> Result<String, OrderError> {
let raw = fs::read_to_string(path).map_err(|e| {
// Add context-specific wrapping
OrderError::Io(e)
})?;
Ok(raw)
}
// ============================================================
// 8) Decision tree
// ============================================================
// Writing a library? -> thiserror, typed enum, document each variant
// Writing an application? -> anyhow::Result + .context() at boundaries
// Internal helper? -> ? + propagate, let the caller decide
// Recoverable vs fatal? -> Result for recoverable; panic! for invariants
struct Config;
fn serve(_: &Config) -> Result<()> { Ok(()) }
Why it matters
`.context("doing X")?` on every fallible call at a logical boundary is what makes anyhow worth using — the resulting error chain reads like a stack trace written by a human, and locating the bug becomes "open the file mentioned in the deepest cause" instead of "diff your changes against last week".
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
use anyhow::{Context, Result};
fn run() -> Result<()> {
let data = std::fs::read_to_string("input.txt")
.context("reading input")?;
println!("{} bytes", data.len());
Ok(())
}
Try it Yourself »
Discussion
Loading…