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 AnyError
Copyright 2025 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.
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.