RustArena

anyhow vs thiserror in Rust: which should you use?

The difference between anyhow and thiserror for Rust error handling, when to use each, with context(), bail!, #[error] and #[from] examples.

Two crates dominate Rust error handling, and they solve different problems. The short version: thiserror for libraries, anyhow for applications, and many projects use both.

thiserror anyhow
What it gives you a derive that writes Display, Error and From for your own error enum one catch-all error type, anyhow::Error
Callers can match on the error yes, it's a normal enum not easily (only by downcasting)
Adding context add fields to variants .context("...") on any Result
Best for libraries, modules with errors callers handle differently binaries, CLIs, main, glue code, tests

thiserror: typed errors without boilerplate

use thiserror::Error;

#[derive(Debug, Error)]
pub enum StoreError {
    #[error("item `{0}` not found")]
    NotFound(String),
    #[error("only {available} left, asked for {requested}")]
    OutOfStock { requested: u32, available: u32 },
    #[error("invalid quantity")]
    BadQuantity(#[from] std::num::ParseIntError),
}

anyhow: one error type for the whole application

use anyhow::{bail, Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let text = std::fs::read_to_string(path)
        .with_context(|| format!("reading {path}"))?;
    let config: Config = toml::from_str(&text).context("parsing config")?;
    if config.workers == 0 {
        bail!("workers must be at least 1");
    }
    Ok(config)
}
Error: reading /etc/arena.toml

Caused by:
    No such file or directory (os error 2)

Print a chain yourself with {:#} (one line, causes joined with : ) or {:?} (the multi-line report).

How they fit together

A typical service: each module (database, payments, parsing) defines a thiserror enum that callers inside the codebase can match on. Request handlers and main use anyhow::Result and .context(), converting module errors with ?, and log the full chain at the top. When code really needs to inspect an anyhow::Error, err.downcast_ref::<StoreError>() recovers the typed error.

Common mistakes

Example

use anyhow::{Context, Result, bail, ensure};
use thiserror::Error;

// A "library" error: callers can match on it.
#[derive(Debug, Error)]
enum StoreError {
    #[error("item `{0}` not found")]
    NotFound(String),
    #[error("only {available} left, asked for {requested}")]
    OutOfStock { requested: u32, available: u32 },
    #[error("invalid quantity")]
    BadQuantity(#[from] std::num::ParseIntError),
}

fn reserve(stock: &[(&str, u32)], item: &str, qty_text: &str) -> Result<u32, StoreError> {
    let requested: u32 = qty_text.trim().parse()?; // ParseIntError -> StoreError via #[from]
    let (_, available) = stock.iter().find(|(name, _)| *name == item).ok_or_else(|| StoreError::NotFound(item.to_string()))?;
    if requested > *available {
        return Err(StoreError::OutOfStock { requested, available: *available });
    }
    Ok(available - requested)
}

// "Application" code: anyhow plus context.
fn handle_order(stock: &[(&str, u32)], line: &str) -> Result<String> {
    let (item, qty) = line.split_once(':').context("expected `item:qty`")?;
    ensure!(!item.is_empty(), "empty item name");
    if item == "banned" {
        bail!("item `{item}` cannot be sold");
    }
    let left = reserve(stock, item, qty).with_context(|| format!("processing order {line:?}"))?;
    Ok(format!("reserved {item}, {left} left"))
}

fn main() {
    let stock = [("mug", 5), ("tee", 1)];
    for line in ["mug:2", "tee:3", "hat:1", "mug:two", "banned:1", "no colon"] {
        match handle_order(&stock, line) {
            Ok(msg) => println!("ok    {msg}"),
            Err(e) => {
                println!("error {e:#}");
                if let Some(StoreError::OutOfStock { available, .. }) = e.downcast_ref::<StoreError>() {
                    println!("      (could offer {available} instead)");
                }
            }
        }
    }
}

Output:

ok    reserved mug, 3 left
error processing order "tee:3": only 1 left, asked for 3
      (could offer 1 instead)
error processing order "hat:1": item `hat` not found
error processing order "mug:two": invalid quantity: invalid digit found in string
error item `banned` cannot be sold
error expected `item:qty`

Practice it