Crate whereat

Crate whereat 

Source
Expand description

§whereat - Lightweight error location tracking

150x faster than backtrace — production error tracing without debuginfo, panic, or overhead.

Error: UserNotFound
   at src/db.rs:142:9
      ╰─ user_id = 42
   at src/api.rs:89:5
      ╰─ in handle_request
   at myapp @ https://github.com/you/myapp/blob/a1b2c3d/src/main.rs#L23

§Try It Now

No setup required — just wrap errors with at() and propagate with .at():

use whereat::{at, At, ResultAtExt};

#[derive(Debug)]
enum MyError { NotFound }

fn inner() -> Result<(), At<MyError>> {
    Err(at(MyError::NotFound))  // Wrap error, capture location
}

fn outer() -> Result<(), At<MyError>> {
    inner().at_str("looking up user")?;  // Add context
    Ok(())
}

let err = outer().unwrap_err();
println!("{:?}", err);  // Shows locations + context

§Production Setup

For clickable GitHub links in traces, add one line to your crate root and use at!():

// In lib.rs or main.rs
whereat::define_at_crate_info!();

fn find_user(id: u64) -> Result<String, At<MyError>> {
    Err(at!(MyError::NotFound))  // Now includes repo links in traces
}

The at!() macro desugars to:

At::wrap(err)
    .set_crate_info(crate::at_crate_info())  // Enables GitHub/GitLab links
    .at()                                     // Captures file:line:col

§Which Approach?

SituationUse
Existing struct/enum you don’t want to modifyWrap with At<YourError>
Want traces embedded inside your error typeImplement AtTraceable trait

Wrapper approach (most common): Return Result<T, At<YourError>> from functions.

Embedded approach: Implement AtTraceable and store an AtTrace (or Box<AtTrace>) field inside your error type. Return Result<T, YourError> directly.

§Starting a Trace

FunctionCrate infoUse when
at(err)❌ NonePrototyping — no setup needed
at!(err)✅ GitHub linksProduction — requires define_at_crate_info!()
err.start_at()❌ NoneChaining on Error trait types

Start with at() to try things out. Upgrade to at!() before shipping — you’ll want those clickable links when debugging production issues.

§Extending a Trace

Create a new location frame (call site is recorded):

MethodEffect
.at()New frame with just file:line:col
.at_fn(|| {})New frame + captures function name
.at_named("step")New frame + custom label

Add context to the last frame (no new location):

MethodEffect
.at_str("msg")Static string (zero-cost)
.at_string(|| format!(...))Dynamic string (lazy)
.at_data(|| value)Typed via Display (lazy)
.at_debug(|| value)Typed via Debug (lazy)
.at_error(source_err)Attach a source error

Key distinction: .at() creates a NEW frame. .at_str() and friends add to the LAST frame.

use whereat::{at, At, ResultAtExt};

#[derive(Debug)]
struct MyError;

fn example() -> Result<(), At<MyError>> {
    // One frame with two contexts attached
    let e = at(MyError).at_str("a").at_str("b");
    assert_eq!(e.frame_count(), 1);

    // Two frames: at() creates first, .at() creates second
    let e = at(MyError).at().at_str("on second frame");
    assert_eq!(e.frame_count(), 2);
    Ok(())
}

§Foreign Crates and Errors

When consuming errors from other crates, use at_crate!() to mark the boundary. This ensures traces show your crate’s GitHub links, not confusing paths from dependencies.

whereat::define_at_crate_info!();  // Once in lib.rs

use whereat::{at_crate, At, ResultAtExt};

fn call_dependency() -> Result<(), At<DependencyError>> {
    at_crate!(dependency::do_thing())?;  // Marks crate boundary
    Ok(())
}

The at_crate!() macro desugars to:

result.at_crate(crate::at_crate_info())  // Adds boundary marker with your crate's info

For plain errors without traces (e.g., std::io::Error), use map_err(at) to start tracing:

use whereat::{At, at, ResultAtExt};

fn external_api() -> Result<(), &'static str> {
    Err("external error")
}

fn wrapper() -> Result<(), At<&'static str>> {
    external_api().map_err(at).at_str("calling external API")?;
    Ok(())
}

§Design Goals

  • Tiny overhead: At<E> is sizeof(E) + 8 bytes; zero heap allocation on the Ok path
  • Zero-cost context: .at_str("literal") stores a pointer, no copy or allocation
  • Lazy evaluation: .at_string(|| ...) closures only run on error
  • no_std compatible: Works with just core + alloc

§OOM Behavior

Trace allocations are fallible where possible — on OOM, trace entries are silently skipped but your error E always propagates (it’s stored inline). See the README for details.

Modules§

prelude
Convenient re-exports for common usage.

Macros§

at
Start tracing an error with crate metadata for repository links.
at_crate
Add crate boundary marker to a Result with an At<E> error.
define_at_crate_info
Define crate-level error tracking info. Call once in your crate root (lib.rs or main.rs).

Structs§

At
An error with location tracking - wraps any error type.
AtContextRef
A reference to context data attached to a trace location.
AtCrateInfo
AtCrateInfoBuilder
Builder for AtCrateInfo with a fluent, const-compatible API.
AtFrame
A single frame in a trace: location with its associated contexts.
AtTrace
Trace storage for location and context tracking.

Constants§

AT_MAX_CONTEXTS
Maximum number of context entries in a trace.
AT_MAX_FRAMES
Maximum number of location frames in a trace.

Traits§

AtTraceable
Trait for types that embed an AtTrace directly.
ErrorAtExt
Extension trait that allows calling .start_at() on error types.
ResultAtExt
Extension trait for adding location tracking to Result<T, At<E>>.
ResultAtTraceableExt
Extension trait for Result<T, E> where E implements AtTraceable.

Functions§

at
Wrap any value in At<E> and capture the caller’s location.