Skip to main content

ErrorStack

Derive Macro ErrorStack 

Source
#[derive(ErrorStack)]
{
    // Attributes available to this derive:
    #[source]
    #[stack_source]
    #[location]
}
Expand description

Derive macro for ErrorStack.

Supports enums and structs with named fields. Note that the type must also implement Display and Error. This can be accomplished manually or via thiserror.

This macro implements ErrorStack according to field names and attributes, and generates an ergonomic constructor for each struct or enum variant that captures caller location via #[track_caller] and composes naturally with [Result::map_err] for error chaining.

§Attributes

The following field attributes are available:

AttributeEffectAuto-detected
#[source]Marks a field as the error source.when field is named source
#[stack_source]Marks the field as both the error source and an ErrorStack implementor, enabling typed chain walking via ErrorStack::stack_source. Implies #[source].no
#[location]Indicates the field stores a &'static Location<'static>, captured automatically at construction time.no

These attributes follow the same field conventions as thiserror, allowing both crates to be ergonomically used together.

§Stack sources

Any source field that implements ErrorStack should be annotated with #[stack_source] to preserve the typed error chain. The macro cannot inspect trait implementations, so without this annotation the source is treated as a plain [std::error::Error] and chain walking stops at that field.

§Optional sources

A source field may be wrapped in [Option] to represent errors that do not always have an underlying cause. When the macro detects an Option<T> source field, it generates two constructors instead of one:

ConstructorSignatureSource value
variant_name / new(user_fields…) -> SelfNone
variant_name_with / new_with(user_fields…) -> impl FnOnce(T) -> SelfSome(source)

§Error constructors

This macro also generates helper constructors for each struct or enum variant. Every constructor is marked #[track_caller], so the call-site location is recorded without manual boilerplate. When a source field is present alongside user fields, the constructor returns impl FnOnce(SourceTy) -> Self, capturing the user fields and composing directly with [Result::map_err]. When the source is the only field, the constructor takes it as a parameter and returns Self directly.

Constructors are pub(crate) and named new for structs or snake_cased_variant for enum variants. Remaining fields become parameters, while #[source] and #[location] fields are filled automatically.

§Examples

The macro may be derived on enums and structs with named fields. This example shows both, with thiserror compatibility.

#[derive(thiserror::Error, ErrorStack, Debug)]
pub enum AppError {
    #[error("io failed: {path}")]
    Io {
        path: String,
        source: std::io::Error,
        #[location]
        location: &'static std::panic::Location<'static>,
    },

    #[error("inner failed")]
    Inner {
        #[stack_source]
        source: ConfigError,
        #[location]
        location: &'static std::panic::Location<'static>,
    },

    #[error("not found: {id}")]
    NotFound {
        id: String,
        #[location]
        location: &'static std::panic::Location<'static>,
    },
}

#[derive(thiserror::Error, ErrorStack, Debug)]
#[error("config: {detail}")]
pub struct ConfigError {
    detail: String,
    #[location]
    location: &'static std::panic::Location<'static>,
}

The derive above generates the following constructors:

impl AppError {
    // Source variants return a closure for use with map_err.
    pub(crate) fn io(path: String) -> impl FnOnce(io::Error) -> Self;
    pub(crate) fn inner(source: ConfigError) -> Self;
    // Sourceless variants return Self directly.
    pub(crate) fn not_found(id: String) -> Self;
}

impl ConfigError {
    pub(crate) fn new(detail: String) -> Self;
}

Source and location fields are handled automatically by these constructors, keeping call sites concise:

let _content = std::fs::read_to_string("Cargo.toml")
    .map_err(AppError::io("Cargo.toml".into()))?;

let _err = AppError::not_found("abc".into());