#error #traits #try #unstable #tracing #call-stack #time

nightly traced_result

Proof-of-concept error tracing using the Try trait

1 unstable release

0.1.0 Jul 25, 2024

#1107 in Rust patterns

MIT license

13KB
192 lines

traced_result

An proof-of-concept to automatically backtrace errors propagated with the ? operator#

Note: This crate relies on the unstable try_trait_v2 language feature. This means it can only be used with the nightly toolchain, may break at any time, and is thus not recommended for use in production code until this feature is stabilized.

Usage

traced_result differs from crates like trace_error in that it does not use macros to trace call stacks, but instead uses the (currently unstable) Try trait to be as consistent with regular Results as possible. The two types at the core of this crate are TracedResult<T, E>, designed to work like std::result::Result<T, E>, and TracedError<E>, which is simply a wrapper around E and a Vec<&'static Location<'static>>. To get started, simply replace Result with TracedResult:

// From
fn foo() -> Result<Bar, Baz> {
    // ...
    return Err(Baz(/*...*/));
}

// To
fn foo() -> TracedResult<Bar, Baz> {
    // ...
    return TracedResult::Err(TracedError::new(Baz(/*...*/)))
}

TracedResult and TracedError also come with convenient From implementations, allowing you to write something like

    return TracedResult::Err(Baz(/*...*/).into()) 

or even

return Err(Baz(/*...*/)).into() 

Now, whenever a TracedResult is propagated with the ? operator, TracedResult's Try impl will store the location of the operators usage to the errors call stack, if any:

fn foo() -> TracedResult<Bar, Baz> {
    Err(Baz(/*...*/)).into()
}

fn do_something() -> TracedResult<(), Baz> {
    let value = foo()?; 
    Ok(())
}

fn main() {
    if let TracedResult::Err(error) = do_something() {
        println!("{}", error) 
        // Baz at (40:21) in file example.rs
        // at (2:26) in file example.rs
    }
}

Result methods

TracedResult<T, E> currently has its the following methods:

  • unwrap() and all related methods, including the unchecked methods
  • is_ok() and is_err()
  • map() and all related methods
  • conversion to an std::result::Result<T, TracedError<E>> using into_result() or the From trait for compatibility any remaining methods – note that subsequent uses of the ? operator will no longer be tracked. To discard the call stack completely, you can also use TracedResult::discard_call_stack() to get a Result<T, E> without the TracedError wrapper around E.

Note: the #[track_caller] attribute

Internally, TracedResult uses the #[track_caller] attribute to get the location at which the ? operator was used. This means that if the result is propagated from a function which itself is annotated with #[track_caller], the Location added to the call stack will be that of the function's caller, not that of the Try operator itself.

No runtime deps