An Rust error library that supports tracking the call-site location of errors.
This crate provides a StackError trait and proc macro to ergonomically work with nested
enum and struct errors, while allowing to track the call-site location for the
full error chain.
It also has a AnyError type that works similar to anyhow errors while keeping
the location metadata of StackError errors accessible through the full error chain.
use n0_error::{e, ensure, Result, StackResultExt, stack_error};
/// The `stack_error` macro controls how to turn our enum into a `StackError`.
///
/// * `add_meta` adds a field to all variants to track the call-site error location
/// * `derive` adds `#[derive(StackError)]`
/// * `from_sources` creates `From` impls for the error sources
#[stack_error(derive, add_meta, from_sources)]
enum MyError {
/// We can define the error message with the `error` attribute
#[error("invalid input")]
InvalidInput { source: InvalidInput },
/// Or we can define a variant as `transparent`, which forwards the Display impl to the error source.
#[error(transparent)]
Io {
/// For sources that do not implement `StackError`, we have to mark the source as `std_err`.
#[error(std_err)]
source: std::io::Error,
},
}
/// We can use the [`stack_error`] macro on structs as well.
#[stack_error(derive, add_meta)]
#[error("wanted {expected} but got {actual}")]
struct InvalidInput {
expected: u32,
actual: u32,
}
fn validate_input(input: u32) -> Result<(), InvalidInput> {
if input != 23 {
// With the `e` macro we can construct an error directly without spelling out the `meta` field:
return Err(e!(InvalidInput { expected: 12, actual: input }))
}
/// There's also `bail` and `ensure` macros that expand to include the `meta` field:
n0_error::ensure!(input == 23, InvalidInput { expected: 23, actual: input });
Ok(())
}
/// The `Result` type defaults to `AnyError` for the error variant.
///
/// Errors types using the derive macro convert to `AnyError`, and we can add additional context
/// with the result extensions.
fn process(input: u32) -> Result<()> {
validate_input(input).context("failed to process input")?;
Ok(())
}The error returned from process would look like this in the alternate display format:
failed to process input: invalid input: wanted 23 but got 13
and like this in the debug format with RUST_BACKTRACE=1 or RUST_ERROR_LOCATION=1:
failed to process input (examples/basic.rs:61:17)
Caused by:
invalid input (examples/basic.rs:36:5)
wanted 23 but got 13 (examples/basic.rs:48:13)
- All errors using the macro implement the
StackErrortrait, which exposes call-site metadata for where the error occurred. Itssourcemethod returns references which may be other stack errors, allowing access to location data for the entire error chain. - The proc macro can add a
metafield to structs or enum variants. This field stores call-site metadata accessed through theStackErrortrait.- Call-site metadata in the
metafield is collected only ifRUST_BACKTRACE=1orRUST_ERROR_LOCATION=1env variable is set. Otherwise, it is disabled to avoid runtime overhead. - The declarative macro
e!provides an ergonomic way to construct such errors without explicitly setting the field. The crate'sensureandbailmacro also do this.
- Call-site metadata in the
- The crate includes an
AnyErrortype, similar toanyhow::Error. When created from aStackError, the call-site metadata is preserved.AnyErroris recommended for applications and tests, while libraries should use concrete derived errors.- All stack errors convert to
AnyError, so they can be propagated to such results with?. - For std errors, use
std_contextoranyerrto convert toAnyError. For stack errors, usecontextor simply propagate with?. - Result extension traits provide conversions between results with
StackErrors,std::error::Errors toAnyError, with support for attaching context.
- All stack errors convert to
- Both
AnyErrorand all errors using theStackErrorderive feature consistent, structured output that includes location metadata when available.
anyhow(off by default): EnablesFrom<anyhow::Error> for AnyErrorandimpl StackError for anyhow::Error
Copyright 2026 N0, INC.
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
This project follows the iroh contributing guidelines. Please read them, along with our AI Policy, before opening an issue or pull request.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.