---
title: Error Trait and Ecosystem
url: https://doc.liz6.com/en/rust/05-error-handling/02-error-trait-and-ecosystem
locale: en
area: rust
tags:
- rust
- error-handling
date: null
modified: 2026-07-16
description: Error Trait and Ecosystem std::error::Error is merely a combination of Display + Debug + source()—it is too thin. thiserror uses derive macros to generate struc…
---

# Error Trait and Ecosystem

> `std::error::Error` is merely a combination of `Display + Debug + source()`—it is too thin. `thiserror` uses derive macros to generate structured error types for libraries (allowing callers to match), while `anyhow` provides a universal error wrapper for applications that "doesn't care about the error type, just propagates it upward." Libraries use `thiserror`, applications use `anyhow`—this is not dogma, but a division of labor refined through practice.

## std::error::Error trait

The standard library's `Error` trait is simple—`Display + Debug + source()`:

```rust
use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct MyError { msg: String, source: Option<Box<dyn Error + 'static>> }

impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", self.msg)
}}

impl Error for MyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> { self.source.as_deref() }
}
```

The `source()` method returns the underlying cause, forming an error chain. `anyhow` and `eyre` leverage this chain to automatically generate backtraces.

## thiserror: derive macro for library crates

Used for **libraries**—providing concrete, matchable error types to callers:

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data not found: {0}")]
    NotFound(String),
    #[error("IO error")]
    Io(#[from] std::io::Error),      // #[from] = auto impl From + auto source
    #[error("invalid config key={key}")]
    InvalidConfig { key: String, detail: String },
}
```

`#[from]` automatically generates `From<io::Error> for DataStoreError`, allowing the `?` operator to perform automatic conversion. Callers can use `match` to distinguish error types.

## anyhow: generic error container for application code

Used for **applications/binaries**—collecting error chains without caring about specific types:

```rust
use anyhow::{Context, Result};

fn read_config(path: &str) -> Result<Config> {
    let file = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read config from {}", path))?;
    let config: Config = toml::from_str(&file)
        .context("invalid TOML format")?;
    Ok(config)
}
```

`anyhow::Error` can store any `Error + Send + Sync + 'static`. `context()` adds human-readable comments to the error chain—this is the most common pattern in anyhow.

## When to use thiserror vs anyhow

A simple rule: **Libraries use thiserror (giving callers choice), applications use anyhow (collecting error context)**. Libraries should not force callers to use a specific error reporting library—throw concrete, matchable types. Applications do not need to distinguish error types—they need all errors to be caught, logged, and have sufficient context to reproduce bugs.

## References

- **thiserror**: docs.rs/thiserror
- **anyhow**: docs.rs/anyhow

*Keywords: Error trait, thiserror, anyhow, context, source, error chain, #[from]*
