1 unstable release

Uses new Rust 2024

new 0.1.1 Apr 5, 2025
0.1.0 Apr 4, 2025

#528 in Rust patterns

Download history 260/week @ 2025-04-02

260 downloads per month

MIT/Apache

15KB
90 lines

locate-error: A poor man's backtrace for thiserror

locate-error is a fairly minimal procedural macro which simply adds location information (file, line, column) to where errors occur wwhen the From<Error> for OtherError trait is called on Error, typically triggered on ? when raising errors across functions. This is intended to be used with thiserror::Error when using the #[from] attribute. This is useful with nested types which derive thiserror::Error and you want to bubble up errors recursively, keeping location information for where each intermediate error occurred. thiserror does support std::backtrace::Backtrace but as of this writing requires a nightly compiler.

The goal is to help users have another resource to add useful context to error messages to avoid unhelpful messages where the display/debug info of the bottom most error is bubbled up without context.

$ cargo run
Error: Program ended

(this was created by a real program)

An output like the above is created by running the example program below but replacing #[locate_from] with the vanilla #[from] attribute and removing the associated Location information.

This is intended to supplement other crates such as anyhow which can provide context for errors. snafu is an alternative which has stable backtrace support.

Example Usage

[dependencies]
locate-error = "0.1"

Before describing the components, an example gives more context on usage. A typical use with thiserror with nested errors would be:

use locate_error::Locate;
use locate_error::Location;
use locate_error::location;
use thiserror::Error;


#[derive(Error, Debug, Locate)]
pub enum OuterError {
    #[error("{0} \n\toccurred at {1}")]
    MiddleError(#[locate_from] MiddleError, Location),
}

#[derive(Error, Debug, Locate)]
#[error("{inner_error} \n\toccurred at {location}")]
pub struct MiddleError {
    #[locate_from]
    inner_error: InnerError,
    location: Location,
}

#[derive(Error, Debug)]
#[error("{message} \n\toccurred at {location}")]
pub struct InnerError {
    message: String,
    location: Location,
}

fn main() {
    let err: OuterError = raise_middle_error().unwrap_err().into();
    println!("{:}", err);
}

fn raise_middle_error() -> Result<(), MiddleError> {
    raise_inner_error()?;
    Ok(())
}

fn raise_inner_error() -> Result<(), InnerError> {
    Err(InnerError {
        message: format!("Error: Program ended"),
        location: location!(),
    })
}

Which outputs

Exception raised in a local function 
        occurred at app/src/bin/locate_error.rs:40:19 
        occurred at app/src/bin/locate_error.rs:33:5 
        occurred at app/src/bin/locate_error.rs:28:61

Components

This crate introduces only a few components:

  • The Location type which holds a file, column, and line number
  • The Locate derive macro which uses the #[locate_from] attribute to implement the From<Inner> for Outer trait for the modified inner error type.
  • The location macro which returns a Location corresponding to the call site

Enum variants or structs that use the #[locate_from] attribute must also include a field of type Location which will be automatically populated with the location where the From trait is called. Since an additional field is added, thiserror attributes such as #[error(transparent)] do not work, so a display message must be provided.

License

Licensed under Apache License 2.0 or MIT at your selection

Dependencies

~1–8MB
~73K SLoC