Skip to content

thiserror

Developer UtilitiesUtilitiesRust

What it is

thiserror derives the std::error::Error implementation for your custom error enums, including display messages and source chaining.

Derive Error on an enum and annotate each variant with a display message. #[from] generates conversions so the ? operator works across error types.

Installation

cargo add thiserror

Getting started

The smallest useful thing you can do with it, and what each part means.

A typed error enum
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("could not read {path}")]
    Io {
        path: String,
        #[source] source: std::io::Error,
    },

    #[error("invalid TOML")]
    Parse(#[from] toml::de::Error),

    #[error("missing required key `{0}`")]
    MissingKey(String),
}

fn load(path: &str) -> Result<Config, ConfigError> {
    let text = std::fs::read_to_string(path)
        .map_err(|source| ConfigError::Io { path: path.into(), source })?;
    Ok(toml::from_str(&text)?)   // converts via #[from]
}
#[from] generates the From impl that lets ? convert automatically, while #[source] preserves the underlying cause so callers can inspect the whole chain.

Advanced usage

Where the library earns its place over a simpler alternative.

Matching on error variants
match load("app.toml") {
    Ok(config) => run(config),
    Err(ConfigError::Io { source, .. })
        if source.kind() == std::io::ErrorKind::NotFound => run(Config::default()),
    Err(e) => {
        eprintln!("{e}");
        // Walk the chain for the full context.
        let mut cause = e.source();
        while let Some(c) = cause {
            eprintln!("  caused by: {c}");
            cause = c.source();
        }
    }
}
This is why libraries should use thiserror rather than anyhow: callers can match on specific failures and recover, which an opaque boxed error makes impossible.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

? does not convert the error type
Add #[from] to the variant wrapping that error type — a plain field does not generate the conversion.
Two variants both use #[from] for the same type
Only one From impl per type is possible. Use #[source] with an explicit constructor for the second.

Best practices

  • Use thiserror in libraries and anyhow in binaries — that split is the ecosystem convention.
  • Add #[source] or #[from] so the underlying cause is preserved.
  • Write display messages in lower case without trailing punctuation; they are composed into chains.
  • Do not leak internal error types through your public API unless they are part of the contract.

Background

Why it exists, and what it was reacting to.

From David Tolnay, thiserror is the library half of Rust's error-handling convention: define precise error types for a library, and use anyhow in the application that consumes it.