# traced_error
Typed errors with automatic trace frames and structured context for Rust error propagation.
`traced_error` keeps the original typed error body while recording where errors are propagated through `?`.
It is useful when you want debugging context similar to `anyhow`, but still need to match concrete error variants and inspect structured fields.
## Why
Rust's `?` operator propagates errors, but it does not automatically record the source location where propagation happened.
Libraries such as `anyhow` are excellent for application-level error reports, but they erase the concrete error type behind a dynamic report.
`traced_error` separates these two concerns:
- `Traced<E>` keeps `E` as the typed error body.
- The `#[traced]` macro appends source locations for each rewritten `?`.
- `.ctx(...)` and `.with_ctx(...)` attach explicit displayable context payloads.
## Features
- Preserve typed error bodies instead of converting everything into a string report.
- Automatically append source locations for `?` propagation through the `#[traced]` macro.
- Attach explicit structured context with `.ctx(...)` and `.with_ctx(...)`.
- Lift raw `Result<T, E>` values into `Result<T, Traced<E>>` with `.ctx_lift(...)` and `.with_ctx_lift(...)`.
- Keep traces and context across error type conversions.
- Implement `std::error::Error` for `Traced<E>` when `E` is an error, exposing the typed body as `source()`.
- Use `SmallVec` to avoid heap allocation for short trace/context stacks.
## Install
```toml
[dependencies]
traced_error = "0.1"
thiserror = "1"
```
You normally only depend on `traced_error` directly. The companion proc-macro crate `traced_macro` is re-exported by `traced_error`.
## Example
```rust
use std::num::ParseIntError;
use traced_error::prelude::*;
#[derive(Debug, thiserror::Error)]
enum MyError {
#[error("parse failed: {0}")]
Parse(#[from] ParseIntError),
#[error("bad input")]
BadInput,
}
#[traced]
fn parse_one(s: &str) -> Result<i64, Traced<MyError>> {
if s.is_empty() {
return Err(Traced::new(MyError::BadInput).ctx("input was empty"));
}
let n = s
.parse()
.with_ctx_lift(|| format!("while parsing {s:?}"))?;
Ok(n)
}
#[traced]
fn parse_two(a: &str, b: &str) -> Result<i64, Traced<MyError>> {
let x = parse_one(a).ctx("first operand")?;
let y = parse_one(b).ctx("second operand")?;
Ok(x + y)
}
fn main() {
match parse_two("12", "not a number") {
Ok(value) => println!("ok: {value}"),
Err(err) => println!("{err}"),
}
}
```
The error still contains `MyError`, so callers can continue to match on typed variants when needed.
When the typed error body implements `std::error::Error`, `Traced<E>` also implements `std::error::Error` and exposes the inner typed error through `source()`.
## Comparison with `tracing-error`
This crate solves a different problem from [`tracing-error`](https://crates.io/crates/tracing-error).
`tracing-error` captures the current [`tracing`](https://crates.io/crates/tracing) span context and attaches it to errors through `SpanTrace` and `TracedError`. It is a good fit when your application is already instrumented with `tracing` spans and you want span-aware diagnostics.
`traced_error` records source locations of `?` propagation points by rewriting them in the `#[traced]` procedural macro. It is a good fit when you want typed errors with automatic file/line/column traces showing where errors were propagated.
In short:
- use `tracing-error` when you want span-aware diagnostics in a `tracing`-instrumented application;
- use `traced_error` when you want typed errors with automatic file/line/column traces for `?` propagation.
The two crates can also be complementary: `tracing-error` tells you the active span context, while `traced_error` tells you the concrete `?` propagation path.
## Workspace layout
This repository contains two crates:
| `traced_error` | Public runtime API: `Traced`, context traits, prelude, and macro re-export. |
| `traced_macro` | Proc-macro implementation for `#[traced]`. |
`traced_macro` does **not** need a separate GitHub repository. It can be published as a separate crates.io package while living in the same GitHub workspace repository.
The publish order matters because `traced_error` depends on `traced_macro`:
```bash
cargo publish -p traced_macro --dry-run
cargo publish -p traced_macro
cargo publish -p traced_error --dry-run
cargo publish -p traced_error
```
## Repository metadata
Both crates can point to the same repository:
```toml
repository = "https://github.com/lihaohan/traced_error"
homepage = "https://github.com/lihaohan/traced_error"
```
There is no requirement to create `https://github.com/lihaohan/traced_macro` unless you want to maintain the macro crate independently.
## License
Licensed under either of MIT or Apache-2.0 at your option.