whereat

Know where the bug is at() — without panic!, debuginfo, or overhead. Replace ? with .at()? to get build-time, async-friendly stacktraces with clickable GitHub links.
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
Compatible with no_std, plain enums, structs, thiserror, anyhow — any type with Debug. No changes to your error types required.
Quick Start
at!() creates a traced error. .at()? propagates it. That's it.
// once in lib.rs — enables clickable GitHub links in traces
// For workspace crates: whereat::define_at_crate_info!(path = "crates/mylib/");
define_at_crate_info!;
use *;
Multiple error types
See Avoiding Trace Loss for patterns that silently destroy traces. Full runnable version: examples/readme.rs.
How it works
graph LR
subgraph "At<E>"
direction TB
E["error: E"]
T["trace: 8 bytes"]
end
T -->|"null until error"| Trace
subgraph Trace["AtTrace (heap, 112 bytes default)"]
direction TB
L["locations: InlineVec<4>"]
C["contexts"]
CI["crate_info"]
end
At<E> is sizeof(E) + 8 bytes. The trace pointer is null until an error occurs — zero heap allocation on the Ok path. Each .at() is #[track_caller], so the compiler bakes file:line:col into the binary as static data. No stack walking, no debug symbols.
Frames vs contexts
.at() creates a frame (a new location). .at_str() adds context to the last frame. Multiple contexts can attach to one frame:
at src/db.rs:15:13 ← .at() created this frame
at src/db.rs:89:9 ← .at() created this frame
╰─ user lookup failed ╰─ .at_str() added context
at src/handler.rs:42:5 ← .at() created this frame
╰─ processing request ╰─ .at_str() added context
╰─ request_id = 7 ╰─ .at_data() added context
150x faster than backtrace, zero overhead on the Ok path, ~18ns per frame on error. See PERFORMANCE.md for benchmarks.
API Reference
Starting a trace
| Function | Works on | Crate info | Use when |
|---|---|---|---|
at!(err) |
Any type | ✅ GitHub links | Default — requires define_at_crate_info!() |
at(err) |
Any type | ❌ None | Quick prototyping, no setup |
err.start_at() |
Error types |
❌ None | Chaining on error values |
Extending a trace
On Result<T, At<E>>:
| Method | Effect |
|---|---|
.at() |
New frame at caller's location |
.at_str("msg") |
Static string context on last frame (zero-cost) |
.at_string(|| format!(...)) |
Dynamic string context (lazy) |
.at_fn(|| {}) |
New frame + captures function name |
.at_named("label") |
New frame + custom label |
.at_data(|| value) |
Typed context via Display (lazy) |
.at_debug(|| value) |
Typed context via Debug (lazy) |
.at_aside_error(err) |
Attach a related error (diagnostic only, not in .source() chain) |
.map_err_at(|e| ...) |
Convert error type, preserve trace |
.at() creates a NEW frame. .at_str() and other context methods add to the LAST frame.
Inspecting and decomposing
| Method | Effect |
|---|---|
.error() |
Borrow the inner &E |
.decompose() |
Consume into (E, Option<AtTrace>) — preserves the trace |
.map_error(|e| ...) |
Convert error type inside At<E>, preserving trace |
.frame_count() |
Number of location frames in the trace |
.full_trace() |
Display formatter showing all frames + contexts |
Cross-crate tracing
When consuming errors from other crates, use at_crate!() to mark the boundary:
define_at_crate_info!;
This ensures traces show myapp @ src/lib.rs:42 instead of confusing paths from dependencies. Desugars to result.at_crate(crate::at_crate_info()).
Hot loops
Don't trace inside hot loops. Defer until you exit:
Avoiding Trace Loss
whereat only works if you keep the trace alive as errors propagate. These patterns silently destroy traces — avoid them.
Never use .into_inner() during error propagation
into_inner() is deprecated since 0.1.4 because it discards the trace. Use decompose() to get both error and trace, or map_error() / map_err_at() to convert types while preserving the trace.
// WRONG — trace is gone
let bare_err = at_err.into_inner;
return Err;
// RIGHT — trace preserved
return inner_call.map_err_at;
Never implement From<At<X>> for Y
This gets invoked by ? and discards the At<> wrapper (and its trace):
// WRONG — ? uses this From impl, trace dies
// RIGHT — implement From on the bare types, convert with map_err_at
Never format-then-rewrap
// WRONG — inner trace is gone, you only get the adapter's location
.map_err?;
// RIGHT — convert the error type, keep the trace
.map_err_at?;
#[from] doesn't work with At<> — don't reach for .into_inner()
thiserror's #[from] generates From<SubError> for MyError, but ? on Result<T, At<SubError>> needs From<At<SubError>> for At<MyError>, which doesn't exist. The compiler will reject it. The temptation is to "fix" this with .into_inner() — don't. Use map_err_at instead:
// WON'T COMPILE — no From<At<SubError>> for At<MyError>
sub_call?;
// WRONG — compiles but trace dies
sub_call.map_err?;
// RIGHT — trace preserved
sub_call.map_err_at?;
Always trace at error origination
Every Err(MyError::Variant) should be Err(at(MyError::Variant)) or Err(at!(MyError::Variant)). If you skip this, there's no trace to propagate.
Design
You define your own error types. whereat wraps them in At<E> to add location + context + crate tracking. Works with plain enums, structs, thiserror, anyhow — anything with Debug.
| Situation | Use |
|---|---|
| Existing struct/enum you don't want to modify | Wrap with At<YourError> |
| Want traces embedded inside your error type | Implement AtTraceable |
At<E> is no_std (core + alloc). The std feature exists for historical compatibility but is a no-op — core::error::Error is stable since Rust 1.81.
See also: Inline storage features | Workspace layouts | Link format customization | Pretty output
Image tech I maintain
| State of the art codecs* | zenjpeg · zenpng · zenwebp · zengif · zenavif (rav1d-safe · zenrav1e · zenavif-parse · zenavif-serialize) · zenjxl (jxl-encoder · zenjxl-decoder) · zentiff · zenbitmaps · heic · zenraw · zenpdf · ultrahdr · mozjpeg-rs · webpx |
| Compression | zenflate · zenzop |
| Processing | zenresize · zenfilters · zenquant · zenblend |
| Metrics | zensim · fast-ssim2 · butteraugli · resamplescope-rs · codec-eval · codec-corpus |
| Pixel types & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline | zenpipe · zencodec · zencodecs · zenlayout · zennode |
| ImageResizer | ImageResizer (C#) — 24M+ NuGet downloads across all packages |
| Imageflow | Image optimization engine (Rust) — .NET · node · go — 9M+ NuGet downloads across all packages |
| Imageflow Server | The fast, safe image server (Rust+C#) — 552K+ NuGet downloads, deployed by Fortune 500s and major brands |
* as of 2026
General Rust awesomeness
archmage · magetypes · enough · whereat · zenbench · cargo-copter
And other projects · GitHub @imazen · GitHub @lilith · lib.rs/~lilith · NuGet (over 30 million downloads / 87 packages)
License
MIT OR Apache-2.0