wherr Crate Documentation
wherr extends Rust's ? operator to append file and line number details to
errors, aiding in debugging.
wherr is composed of two separate crates, due to Rust's limitation that
prohibits mixing normal functions with procedural macros in a single crate:
wherr: This is the main library that offers the enhanced functionality for error handling in Rust.wherr-macro: Contains the procedural macros specifically designed for thewherrcrate.
Table of Contents
Quick Start
Add the wherr crate to your Cargo.toml:
[]
= "0.1"
Now, by simply annotating your functions with #[wherr], any error propagated
using ? will also include the file and line number.
use wherr;
How it works
The #[wherr] notation is a proc_macro_attribute, a powerful tool in Rust that allows for custom transformations of the code at compile time.
When an error is propagated with the ? operator in a function decorated with
#[wherr], it captures the file and line number where the error occurred.
This provides developers with a more informative error, aiding in precise
debugging.
For instance, consider this function:
Under the hood, the #[wherr] macro transforms this function to:
Now, you may be wondering: How does this wherrapper function make all
this happen?
The wherrapper function receives the original Result, the file, and the line
number. If the Result is an Ok, it's simply returned unchanged.
For an Err, however, things are a bit more intricate.
If the error hasn't already been wrapped in a Wherr type (meaning it doesn't yet
have the file and line details), the function wraps it in a new Wherr type
that contains the original error, as well as the file and line information.
If the error is already a Wherr type, it's left unchanged, ensuring that we
retain the original location of the error.
Here's a brief look at the wherrapper function:
Through this mechanism, any error returned (propagated using ?) from
a function annotated with #[wherr] will the precise location of where the
error occurred in the code.
This offers developers better insight during debugging sessions.
Usage
To understand the benefits of the wherr crate, let's first observe the problem
it aims to solve.
If you already know Rust, feel free to skip ahead to the Without wherr
section, since the following will breifly explain the Rust concepts of
the Result<T, E> type,
and the ? operator.
In these examples, we utilize the i64::from_str_radix(s1, radix) function
from the standard library, which has the signature:
This function aims to convert a string slice, representing a number in a
specified base, into an integer. It returns a Result type —- an enum in Rust.
The Result enum comprises two variants: Ok and Err. Notably, in Rust,
these variants can encapsulate data. For Result<i64, ParseIntError>,
the Ok variant wraps an i64 value, whereas the Err variant encapsulates
a ParseIntError value.
In the line:
let i = i64from_str_radix?;
We see the usage of the ? operator.
This operator is used in Rust for a concise error handling.
When placed after a function that returns a Result, it does two things:
-
If the function returns an
Okvariant, the?operator extracts the value insideOkand assigns it to the variable (in this casei). -
If the function returns an
Errvariant, the?operator immediately returns this error, effectively short-circuiting any further operations in the function.
Let's make an experiment demonstrating both variants.
Without #[wherr]:
examples/basic_without_wherr.rs:
// Function to add two numbers represented as strings.
// Returns a Result with the sum within an `Ok` variant if successful,
// or an `Err` variant if there's an error.
x = Err(ParseIntError { kind: InvalidDigit })
Note that the Err lacks file or line details.
Using .unwrap() extracts the Ok value or panics on error. While the panic
shows the file and line number, it only indicates the .unwrap() location,
not the error's origin. Even with RUST_BACKTRACE=1 or RUST_BACKTRACE=full,
the error's origin remains elusive. As it's a returned value, possibly
passed through many functions, without embedded file or line info, retrieval
is impossible.
examples/unwrap_without_wherr.rs:
// The add() function is as previously defined and is omitted here for clarity.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
ParseIntError { kind: InvalidDigit }', wherr/examples/unwrap_without_wherr.rs:12:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The line wherr/examples/unwrap_without_wherr.rs:12 corresponds to:
let x = add.unwrap;
Now, let's have a look at the same examples, but this time with #[wherr]
enabled.
With #[wherr]:
By adding #[wherr] macro to the function, errors will automatically
be wrapped in a Wherr struct with a file and line field
telling us where the error happened. The original error is preserved and
accessible via the inner field.
examples/basic_with_wherr.rs:
use wherr;
// Function to add two numbers represented as strings.
// Returns a Result with the sum within an `Ok` variant if successful,
// or an `Err` variant if there's an error.
x = Err(Error at wherr/examples/basic_with_wherr.rs:10. Original error: ParseIntError { kind: InvalidDigit })
The line wherr/examples/basic_with_wherr.rs:10 corresponds to:
let i2 = i64from_str_radix?;
examples/unwrap_with_wherr.rs:
use wherr;
// Function to add two numbers represented as strings.
// Returns a Result with the sum within an `Ok` variant if successful,
// or an `Err` variant if there's an error.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
Error at wherr/examples/unwrap_with_wherr.rs:10.
Original error: ParseIntError { kind: InvalidDigit }', wherr/examples/unwrap_with_wherr.rs:15:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The line wherr/examples/unwrap_with_wherr.rs:10 corresponds to:
let i2 = i64from_str_radix?;
And the line wherr/examples/unwrap_with_wherr.rs:15 corresponds to:
let x = add.unwrap;
Here is the diff (additional newlines added for clarity):
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
-ParseIntError { kind: InvalidDigit }', wherr/examples/unwrap_without_wherr.rs:12:40
+Error at wherr/examples/unwrap_with_wherr.rs:10.
+Original error: ParseIntError { kind: InvalidDigit }', wherr/examples/unwrap_with_wherr.rs:15:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The file and line info can also be extracted from the Wherr struct,
that wraps the original Err:
match add
It also works through multiple nested layers of ?. The Err is only wrapped
inside a Wherr once, and then propagated unchanged upwards.
API Reference
Wherr
Represents an error that includes file and line number information.
Methods:
new(err: Box<dyn std::error::Error>, file: &'static str, line: u32) -> Self: Creates a newWherrerror that wraps another error, providing additional context.
wherrapper
This internal utility function is used by the procedural macro to wrap errors with file and line information.
wherr procedural macro
A procedural macro that auto-wraps errors (using the ? operator) inside
a function with file and line number details.
Contributing
If you're interested in contributing to wherr, please follow standard
Rust community guidelines and submit a PR on our repository.
License
Please refer to the LICENSE file in the root directory of the crate for
license details.