pub struct At<E> { /* private fields */ }Expand description
An error with location tracking - wraps any error type.
§Size
At<E> is sizeof(E) + 8 bytes on 64-bit platforms:
- The error
Eis stored inline - The trace is boxed (8-byte pointer, null when empty)
§Equality and Hashing
At<E> implements PartialEq, Eq, and Hash based only on the inner
error E, ignoring the trace. The trace is metadata about where an
error was created, not what the error is.
This means two At<E> values are equal if their inner errors are equal,
even if they were created at different source locations:
use whereat::at;
#[derive(Debug, PartialEq)]
struct MyError(u32);
let err1 = at(MyError(42)); // Created here
let err2 = at(MyError(42)); // Created on different line
assert_eq!(err1, err2); // Equal because inner errors match§Example
use whereat::{at, At};
#[derive(Debug)]
enum MyError { Oops }
// Create a traced error using at() function
let err: At<MyError> = at(MyError::Oops);
assert_eq!(err.frame_count(), 1);§Note: Avoid At<At<E>>
Nesting At<At<E>> is supported but unnecessary and wasteful.
Each At has its own trace, so nesting allocates two Box<AtTrace>
instead of one. Use .at() on Results to extend the existing trace:
use whereat::{at, At};
#[derive(Debug)]
struct MyError;
// GOOD: Extend existing trace
fn good() -> Result<(), At<MyError>> {
let err: At<MyError> = at(MyError);
Err(err.at()) // Same trace, new location
}
// UNNECESSARY: Creates two separate traces
fn unnecessary() -> At<At<MyError>> {
at(at(MyError)) // Two allocations
}Implementations§
Source§impl<E> At<E>
impl<E> At<E>
Sourcepub fn from_parts(error: E, trace: AtTrace) -> At<E>
pub fn from_parts(error: E, trace: AtTrace) -> At<E>
Create an At<E> from an error and an existing trace.
Used for transferring traces between error types.
Sourcepub fn at(self) -> At<E>
pub fn at(self) -> At<E>
Add the caller’s location to the trace.
This is the primary API for building up a stack trace as errors propagate. If allocation fails, the location is silently skipped.
§Example
use whereat::At;
#[derive(Debug)]
enum MyError { Oops }
fn inner() -> Result<(), At<MyError>> {
Err(At::wrap(MyError::Oops).at())
}
fn outer() -> Result<(), At<MyError>> {
inner().map_err(|e| e.at())
}Sourcepub fn at_fn<F>(self, _marker: F) -> At<E>where
F: Fn(),
pub fn at_fn<F>(self, _marker: F) -> At<E>where
F: Fn(),
Add a location frame with the caller’s function name as context.
Captures both file:line:col AND the function name at zero runtime cost.
Pass an empty closure || {} - its type includes the parent function name.
§Example
use whereat::{at, At};
#[derive(Debug)]
enum MyError { NotFound }
fn load_config() -> Result<(), At<MyError>> {
Err(at(MyError::NotFound).at_fn(|| {}))
}
// Output will include:
// at src/lib.rs:10:5
// in my_crate::load_configSourcepub fn at_named(self, name: &'static str) -> At<E>
pub fn at_named(self, name: &'static str) -> At<E>
Add a location frame with an explicit name as context.
Like at_fn but with an explicit label instead of
auto-detecting the function name. Useful for naming checkpoints,
phases, or operations within a function.
§Example
use whereat::{at, At};
#[derive(Debug)]
enum MyError { Failed }
fn process() -> Result<(), At<MyError>> {
// ... validation phase ...
Err(at(MyError::Failed).at_named("validation"))
}
// Output will include:
// at src/lib.rs:10:5
// in validationSourcepub fn at_str(self, msg: &'static str) -> At<E>
pub fn at_str(self, msg: &'static str) -> At<E>
Add a static string context to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
Zero-cost for static strings - just stores a pointer.
For dynamically-computed strings, use at_string().
§Frame behavior
use whereat::at;
#[derive(Debug)]
struct E;
// One frame with two contexts
let e = at(E).at_str("a").at_str("b");
assert_eq!(e.frame_count(), 1);
// Two frames: first from at(), second gets the context
let e = at(E).at().at_str("on second frame");
assert_eq!(e.frame_count(), 2);§Example
use whereat::{at, At, ResultAtExt};
#[derive(Debug)]
enum MyError { IoError }
fn read_config() -> Result<(), At<MyError>> {
Err(at(MyError::IoError))
}
fn init() -> Result<(), At<MyError>> {
read_config().at_str("while loading configuration")?;
Ok(())
}Sourcepub fn at_string(self, f: impl FnOnce() -> String) -> At<E>
pub fn at_string(self, f: impl FnOnce() -> String) -> At<E>
Add a lazily-computed string context to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
The closure is only called on error path, avoiding allocation on success.
For static strings, use at_str() instead for zero overhead.
§Example
use whereat::{at, At, ResultAtExt};
#[derive(Debug)]
enum MyError { NotFound }
fn load(path: &str) -> Result<(), At<MyError>> {
Err(at(MyError::NotFound))
}
fn init(path: &str) -> Result<(), At<MyError>> {
// Closure only runs on Err - no allocation on Ok path
load(path).at_string(|| format!("loading {}", path))?;
Ok(())
}Sourcepub fn at_data<T>(self, f: impl FnOnce() -> T) -> At<E>
pub fn at_data<T>(self, f: impl FnOnce() -> T) -> At<E>
Add lazily-computed typed context (Display) to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
The closure is only called on error path, avoiding allocation on success.
Use for typed data that you want to format with Display and later retrieve
via downcast_ref::<T>().
For plain string messages, prefer at_str() or at_string().
For Debug-formatted data, use at_debug().
§Example
use whereat::{at, At};
#[derive(Debug)]
enum MyError { NotFound }
// Custom Display type for rich context
struct PathContext(String);
impl std::fmt::Display for PathContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "path: {}", self.0)
}
}
fn load(path: &str) -> Result<(), At<MyError>> {
Err(at(MyError::NotFound))
}
fn init(path: &str) -> Result<(), At<MyError>> {
load(path).map_err(|e| e.at_data(|| PathContext(path.into())))?;
Ok(())
}Sourcepub fn at_debug<T>(self, f: impl FnOnce() -> T) -> At<E>
pub fn at_debug<T>(self, f: impl FnOnce() -> T) -> At<E>
Add lazily-computed typed context (Debug) to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
The closure is only called on error path, avoiding allocation on success.
Use contexts() to retrieve entries and
downcast_ref() to access typed data.
§Example
use whereat::at;
#[derive(Debug)]
struct RequestInfo { user_id: u64, path: String }
#[derive(Debug)]
enum MyError { Forbidden }
let err = at(MyError::Forbidden)
.at_debug(|| RequestInfo { user_id: 42, path: "/admin".into() });
// Later, retrieve the context
for ctx in err.contexts() {
if let Some(req) = ctx.downcast_ref::<RequestInfo>() {
assert_eq!(req.user_id, 42);
}
}Sourcepub fn at_error<Err>(self, err: Err) -> At<E>
pub fn at_error<Err>(self, err: Err) -> At<E>
Add an error as context to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
Use this to attach a source error that implements core::error::Error.
The error’s .source() chain is preserved and can be traversed.
§Example
use whereat::at;
use std::io;
#[derive(Debug)]
struct MyError;
fn wrap_io_error(io_err: io::Error) -> whereat::At<MyError> {
at(MyError).at_error(io_err)
}Sourcepub fn at_crate(self, info: &'static AtCrateInfo) -> At<E>
pub fn at_crate(self, info: &'static AtCrateInfo) -> At<E>
Add a crate boundary marker to the last location frame.
Does not add a new location frame - attaches context to the most recent frame in the trace. If the trace is empty, creates a frame at the caller’s location first.
This marks that subsequent locations belong to a different crate, enabling correct GitHub links in cross-crate traces.
Requires define_at_crate_info!() or
a custom at_crate_info() getter.
§Example
// Requires define_at_crate_info!() setup
use whereat::{at, At};
whereat::define_at_crate_info!();
#[derive(Debug)]
enum MyError { Wrapped(String) }
fn wrap_external_error(msg: &str) -> At<MyError> {
at(MyError::Wrapped(msg.into()))
.at_crate(crate::at_crate_info())
}Sourcepub fn set_crate_info(self, info: &'static AtCrateInfo) -> At<E>
pub fn set_crate_info(self, info: &'static AtCrateInfo) -> At<E>
Set the crate info for this trace.
This is used by at!() to provide repository metadata for GitHub links.
Calling this creates the trace if it doesn’t exist yet.
§Example
// Requires define_at_crate_info!() setup
whereat::define_at_crate_info!();
#[derive(Debug)]
enum MyError { Oops }
let err = At::wrap(MyError::Oops)
.set_crate_info(crate::at_crate_info())
.at();Sourcepub fn crate_info(&self) -> Option<&'static AtCrateInfo>
pub fn crate_info(&self) -> Option<&'static AtCrateInfo>
Get the crate info for this trace, if set.
Sourcepub fn into_inner(self) -> E
pub fn into_inner(self) -> E
Consume self and return the inner error, discarding the trace.
Sourcepub fn contexts(&self) -> impl Iterator<Item = AtContextRef<'_>>
pub fn contexts(&self) -> impl Iterator<Item = AtContextRef<'_>>
Iterate over all context entries, newest first.
Each call to at_str(), at_string(), at_data(), or at_debug() creates
a context entry. Use AtContextRef methods to inspect context data.
Note: Prefer frames() for unified iteration over
locations with their contexts.
§Example
use whereat::at;
#[derive(Debug)]
struct MyError;
let err = at(MyError)
.at_str("loading config")
.at_str("initializing");
let texts: Vec<_> = err.contexts()
.filter_map(|ctx| ctx.as_text())
.collect();
assert_eq!(texts, vec!["initializing", "loading config"]); // newest firstSourcepub fn frames(&self) -> impl Iterator<Item = AtFrame<'_>>
pub fn frames(&self) -> impl Iterator<Item = AtFrame<'_>>
Iterate over frames (location + contexts pairs), oldest first.
This is the recommended way to traverse a trace. Each frame contains a location (or None for skipped-frames marker) and its associated contexts.
§Example
use whereat::at;
#[derive(Debug)]
struct MyError;
let err = at(MyError)
.at_str("loading config")
.at();
for frame in err.frames() {
if let Some(loc) = frame.location() {
println!("at {}:{}", loc.file(), loc.line());
}
for ctx in frame.contexts() {
println!(" - {}", ctx);
}
}Sourcepub fn frame_count(&self) -> usize
pub fn frame_count(&self) -> usize
Get the number of frames in the trace.
Sourcepub fn at_pop(&mut self) -> Option<AtFrameOwned>
pub fn at_pop(&mut self) -> Option<AtFrameOwned>
Pop the most recent location and its contexts from the trace.
Returns None if the trace is empty.
Sourcepub fn at_push(&mut self, segment: AtFrameOwned)
pub fn at_push(&mut self, segment: AtFrameOwned)
Push a segment (location + contexts) to the end of the trace.
Sourcepub fn at_first_pop(&mut self) -> Option<AtFrameOwned>
pub fn at_first_pop(&mut self) -> Option<AtFrameOwned>
Pop the oldest location and its contexts from the trace.
Returns None if the trace is empty.
Sourcepub fn at_first_insert(&mut self, segment: AtFrameOwned)
pub fn at_first_insert(&mut self, segment: AtFrameOwned)
Insert a segment (location + contexts) at the beginning of the trace.
Sourcepub fn take_trace(&mut self) -> Option<AtTrace>
pub fn take_trace(&mut self) -> Option<AtTrace>
Take the entire trace, leaving self with an empty trace.
Sourcepub fn map_error<E2, F>(self, f: F) -> At<E2>where
F: FnOnce(E) -> E2,
pub fn map_error<E2, F>(self, f: F) -> At<E2>where
F: FnOnce(E) -> E2,
Convert the error type while preserving the trace.
§Example
use whereat::{at, At};
#[derive(Debug)]
struct Error1;
#[derive(Debug)]
struct Error2;
impl From<Error1> for Error2 {
fn from(_: Error1) -> Self { Error2 }
}
let err1: At<Error1> = at(Error1).at_str("context");
let err2: At<Error2> = err1.map_error(Error2::from);
assert_eq!(err2.frame_count(), 1);Sourcepub fn into_traceable<E2, F>(self, f: F) -> E2where
F: FnOnce(E) -> E2,
E2: AtTraceable,
pub fn into_traceable<E2, F>(self, f: F) -> E2where
F: FnOnce(E) -> E2,
E2: AtTraceable,
Convert to an AtTraceable type, transferring the trace.
The closure receives the inner error and should return an error type
that implements AtTraceable. The trace is then transferred to the
new error’s embedded trace.
§Example
use whereat::{at, At, AtTrace, AtTraceable};
#[derive(Debug)]
struct Inner;
struct MyError {
trace: AtTrace,
}
impl AtTraceable for MyError {
fn trace_mut(&mut self) -> &mut AtTrace { &mut self.trace }
fn trace(&self) -> Option<&AtTrace> { Some(&self.trace) }
fn fmt_message(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "my error")
}
}
let at_err: At<Inner> = at(Inner).at_str("context");
let my_err: MyError = at_err.into_traceable(|_| MyError { trace: AtTrace::new() });Source§impl<E> At<E>where
E: Debug,
impl<E> At<E>where
E: Debug,
Sourcepub fn display_with_meta(&self) -> impl Display
pub fn display_with_meta(&self) -> impl Display
Format the error with GitHub links using AtCrateInfo from the trace.
When you use at!() or .at_crate(), the crate metadata is stored in
the trace. This method uses that metadata to generate clickable GitHub
links for each location.
For cross-crate traces, each at_crate() call updates the repository
used for subsequent locations until another crate boundary is encountered.
§Example
// Requires define_at_crate_info!() setup
use whereat::{at, At};
whereat::define_at_crate_info!();
#[derive(Debug)]
struct MyError;
let err = at!(MyError);
println!("{}", err.display_with_meta());Source§impl<E> At<E>where
E: Display,
impl<E> At<E>where
E: Display,
Sourcepub fn full_trace(&self) -> impl Display
pub fn full_trace(&self) -> impl Display
Format with full trace (message + locations + all contexts).
Returns a formatter that displays:
- The error message (via
Display) - All trace frame locations
- All context strings at each location
§Example
use whereat::{at, At};
#[derive(Debug)]
struct MyError(&'static str);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
let err: At<MyError> = at(MyError("failed")).at_str("loading config");
println!("{}", err.full_trace());
// Output:
// failed
// at src/main.rs:10:1
// loading configSourcepub fn last_error_trace(&self) -> impl Display
pub fn last_error_trace(&self) -> impl Display
Format with trace locations only (message + locations, no context strings).
Returns a formatter that displays:
- The error message (via
Display) - All trace frame locations
- NO context strings (for compact output)
§Example
use whereat::{at, At};
#[derive(Debug)]
struct MyError(&'static str);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
let err: At<MyError> = at(MyError("failed")).at_str("loading config");
println!("{}", err.last_error_trace());
// Output:
// failed
// at src/main.rs:10:1Sourcepub fn last_error(&self) -> impl Display
pub fn last_error(&self) -> impl Display
Format just the error message (no trace).
Returns a formatter that only displays the error message via Display.
Use this when you want to show the error without any trace information.
This is equivalent to using the Display impl directly.