---
title: How to use thiserror crate in Rust error derive
summary: Use the thiserror derive macro to automatically implement error traits and define display messages for custom error enums in Rust.
lang: en
slug: how-to-use-thiserror-crate-in-rust-error-derive
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-thiserror-crate-in-rust-error-derive/
---

# How to use thiserror crate in Rust error derive



## The boilerplate trap

You are building a tool that parses a configuration file. The function returns a `Result`. You define an enum for the errors. Now you need to implement `std::error::Error` and `std::fmt::Display`. You write the `impl Display` block. You format a string. You miss a closing brace. The compiler rejects you with E0277 because your type fails the trait bounds required by the result type. You fix the brace. You realize you also need `impl From<std::io::Error>` so the `?` operator works. You write that impl. You copy-paste the pattern for `serde_json::Error`. You have three error types in your crate. You just wrote forty lines of boilerplate. The logic is identical across all three. You need a way to declare the error structure and let the compiler generate the trait implementations.

This is the job of `thiserror`. The crate provides a derive macro that generates the standard error traits based on attributes you add to your enum. You describe the error variants and the messages. The macro writes the `impl` blocks. You get type-safe errors with zero boilerplate.

## How the macro works

`thiserror` is a procedural macro. You add `#[derive(Error)]` to your enum. The macro comes from `thiserror::Error`, not `std::error::Error`. The naming convention keeps the import explicit and avoids confusion with the standard trait. The macro scans your enum variants and looks for attributes like `#[error(...)]` and `#[from]`.

When you compile, the macro expands into the code you would otherwise write by hand. It generates `impl std::fmt::Display` for the enum. It generates `impl std::error::Error`. If you use `#[from]`, it generates `impl From<Source>` for each variant. The generated code is equivalent to manual implementations, but the macro handles the formatting logic and trait bounds automatically.

Add `thiserror` to your `Cargo.toml`. Add `#[derive(Debug)]` to your error enum as well. The macro requires `Debug` to generate the code. If you forget it, the macro fails with a compile error. Convention is to always derive `Debug` on error types. It helps with logging and debugging. The community expects errors to be debuggable.

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    // Derive Error from thiserror to generate trait impls.
    // Derive Debug because the macro requires it and it aids debugging.
    
    #[error("file not found: {0}")]
    // The message string uses {0} to reference the first field.
    // The macro generates Display impl that formats this string.
    NotFound(String),

    #[error("invalid format: {0}")]
    // Placeholders map to fields by index.
    // {0} refers to the String field in this variant.
    InvalidFormat(String),
}
```

The `#[error("...")]` attribute defines the display message. The macro generates an `impl std::fmt::Display` that formats the string. Placeholders like `{0}` refer to the fields of the variant. The macro handles the formatting logic. You do not write the `match` statement. The macro also generates `impl std::error::Error`. This trait marks the type as a proper error in the Rust ecosystem. It enables methods like `.source()` and integration with panic handling.

Don't write `impl Display` by hand. The macro generates correct formatting code every time.

## The #[from] attribute and the ? operator

The `#[from]` attribute is where `thiserror` saves the most time. When you add `#[from]` to a field, the macro generates an `impl From<FieldType>` for your error enum. This implementation allows the `?` operator to convert the field type into your error automatically. Without `#[from]`, you have to write the conversion manually or use `.map_err`. With `#[from]`, the `?` operator works out of the box.

This connects directly to Rust's error propagation style. You can return `io::Result<T>` and convert to your custom error just by returning the value with `?`. The compiler sees the `From` impl and inserts the conversion. You also get the error chain for free. The macro sets the inner error as the source, so calling `.source()` on your error returns the wrapped error. This preserves the error context through the call stack.

```rust
use std::fs;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("IO error: {0}")]
    // #[from] generates impl From<std::io::Error>.
    // This allows ? to convert io::Error to AppError automatically.
    // The macro also sets io::Error as the source for error chaining.
    Io(#[from] std::io::Error),

    #[error("config key '{0}' is missing")]
    // Custom message with a placeholder.
    // No #[from] here because String is not an error type we convert from.
    MissingKey(String),

    #[error(transparent)]
    // transparent delegates Display to the inner error.
    // The message from serde_json::Error prints directly.
    // #[from] still generates the From impl for ? conversion.
    Parse(#[from] serde_json::Error),
}

fn read_config() -> Result<serde_json::Value, AppError> {
    // fs::read_to_string returns io::Result.
    // The ? operator converts io::Error to AppError::Io via #[from].
    let content = fs::read_to_string("config.json")?;
    
    // serde_json::from_str returns Result with serde_json::Error.
    // The ? operator converts to AppError::Parse via #[from].
    let value: serde_json::Value = serde_json::from_str(&content)?;
    
    Ok(value)
}
```

Use `#[from]` on every external error you wrap. The `?` operator is the standard way to propagate errors in Rust.

## Advanced attributes and conventions

`thiserror` supports attributes for more complex scenarios. The `#[error(transparent)]` attribute is useful when you want to wrap an error without adding a message layer. It delegates `Display` entirely to the inner error. If you use `#[error("{0}")]`, you add a wrapper message. If you use `transparent`, the inner error prints itself. This is the preferred approach when the inner error's message is already clear and adding context would be redundant.

The `#[source]` attribute exposes a field as the error source without generating a `From` impl. Use `#[source]` when you want to keep the error chain for `.source()` but handle conversion manually or via a different mechanism. This is rare. Most code uses `#[from]` for both conversion and chaining.

The `#[backtrace]` attribute captures a backtrace for the variant. This requires the `backtrace` feature in `Cargo.toml`. Backtraces are heavy. They slow down execution and increase binary size. Use them only when you need stack traces for debugging production issues. Convention is to enable backtraces selectively, not on every error.

Convention aside: `thiserror` supports both `#[error("...")]` and `#[display("...")]`. Both work identically. The community convention is `#[error]`. Using `#[display]` works but looks odd to other Rust developers. Stick to `#[error]` for consistency.

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DatabaseError {
    #[error("connection failed: {0}")]
    // Standard error message with placeholder.
    Connection(#[from] std::io::Error),

    #[error(transparent)]
    // Passthrough for driver errors.
    // The driver's message is already descriptive.
    Driver(#[from] tokio_postgres::Error),

    #[error("query timeout")]
    // Static message for a specific condition.
    // No fields, no conversion.
    Timeout,
}
```

Treat `transparent` as a passthrough. Use it when the inner error's message is already clear.

## Pitfalls and compiler errors

Common mistakes trigger specific compiler errors. Forgetting `#[derive(Debug)]` causes the macro to fail. The error message points to the derive macro. Add `Debug` to fix it.

Using `#[from]` on multiple variants for the same source type creates a conflict. The macro generates duplicate `impl From<Source>` blocks. The compiler rejects this with E0119 (conflicting implementations of trait). You can only have one `From` impl per source type. If you need to convert the same error type to different variants, you must implement `From` manually or use a helper function.

Mixing `#[from]` and `#[source]` on the same field is redundant. `#[from]` already sets the source. Adding `#[source]` does nothing extra. The macro handles it, but it clutters the code. Use `#[from]` alone.

Using `#[error]` on a variant with no fields and no message is valid but produces an empty display string. This is usually a mistake. Add a message or remove the attribute if you rely on `Debug` output.

Check for duplicate `#[from]` attributes. The compiler will reject conflicting `From` implementations.

## Decision matrix

Rust has several error handling crates. Choose based on your context.

Use `thiserror` when you are writing a library and need to expose a typed error enum. Libraries should define precise error types so consumers can match on variants. `thiserror` minimizes the boilerplate for defining those types.

Use `anyhow` when you are writing an application and just need a generic error type for the top level. Applications often aggregate errors from many sources. `anyhow` provides a flexible `Result<T, anyhow::Error>` that wraps any error with context. It is heavier but more convenient for app-level code.

Use manual implementations when you have a single error type with complex logic that the macro cannot handle. This is rare. Most error types fit the enum pattern. Manual impls are harder to maintain and prone to typos.

Use `snafu` when you need automatic context attachment and backtrace handling with less boilerplate than manual but more than `thiserror`. `snafu` generates context methods and handles backtraces automatically. It is a heavier dependency.

Libraries use `thiserror`. Applications use `anyhow`. Stick to the convention.

## Where to go next

- [How to use thiserror crate in Rust error types](/en/how-to-use-thiserror-crate-in-rust-error-types/)
- [How to Use the thiserror Crate for Custom Errors](/en/how-to-use-the-thiserror-crate-for-custom-errors/)
- [How to use thiserror crate](/en/how-to-use-thiserror-crate/)