traced_error 0.1.1

Typed errors with automatic trace frames and structured context for Rust error propagation
Documentation
# 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:

| Crate | Purpose |
| --- | --- |
| `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.