# `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:
1. [`wherr`](https://crates.io/crates/wherr):
This is the main library that offers the enhanced functionality for error
handling in Rust.
2. [`wherr-macro`](https://crates.io/crates/wherr-macro):
Contains the procedural macros specifically designed for the `wherr` crate.
## Table of Contents
- [Quick Start](#quick-start)
- [How it works](#how-it-works)
- [Usage](#usage)
- [Without `#[wherr]`](#without-wherr)
- [With `#[wherr]`](#with-wherr)
- [API Reference](#api-reference)
- [`Wherr`](#wherr)
- [`wherrapper`](#wherrapper)
- [`wherr` procedural macro](#wherr-procedural-macro)
- [Contributing](#contributing)
- [License](#license)
## Quick Start
Add the `wherr` crate to your `Cargo.toml`:
```toml
[dependencies]
wherr = "0.1"
```
Now, by simply annotating your functions with `#[wherr]`, any error propagated
using `?` will also include the file and line number.
```rust
use wherr::wherr;
#[wherr]
fn some_function() -> Result<(), Box<dyn std::error::Error>> {
/* ... */
some_operation()?;
/* ... */
}
```
## How it works
The #[wherr] notation is a [proc_macro_attribute](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros),
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:
```rust
#[wherr]
fn add(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> {
let radix = 10;
let i1 = i64::from_str_radix(s1, radix)?;
let i2 = i64::from_str_radix(s2, radix)?;
Ok(i1 + i2)
}
```
Under the hood, the `#[wherr]` macro transforms this function to:
```rust
fn add(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> {
let radix = 10;
let i1 = wherr::wherrapper(i64::from_str_radix(s1, radix), file!(), line!())?;
let i2 = wherr::wherrapper(i64::from_str_radix(s2, radix), file!(), line!())?;
Ok(i1 + i2)
}
```
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:
```rust
pub fn wherrapper<T, E>(
result: Result<T, E>,
file: &'static str,
line: u32,
) -> Result<T, Box<dyn std::error::Error>>
where
E: Into<Box<dyn std::error::Error>>,
{
match result {
Ok(val) => Ok(val),
Err(err) => {
let boxed_err: Box<dyn std::error::Error> = err.into();
if boxed_err.is::<Wherr>() {
Err(boxed_err)
} else {
Err(Box::new(Wherr::new(boxed_err, file, line)))
}
}
}
}
```
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](#without-wherr)
section, since the following will breifly explain the Rust concepts of
the [`Result<T, E>`](https://doc.rust-lang.org/std/result/) type,
and the [`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator).
In these examples, we utilize the `i64::from_str_radix(s1, radix)` function
from the standard library, which has the signature:
```rust
pub fn from_str_radix(src: &str, radix: u32) -> Result<i64, ParseIntError>
```
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:
```rust
let i = i64::from_str_radix(s, radix)?;
```
We see the usage of the `?` [operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-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:
1. If the function returns an `Ok` variant, the `?` operator extracts the value
inside `Ok` and assigns it to the variable (in this case `i`).
2. If the function returns an `Err` variant, 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`:
```rust
// 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.
fn add(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> {
let radix = 10;
let i1 = i64::from_str_radix(s1, radix)?;
let i2 = i64::from_str_radix(s2, radix)?;
Ok(i1 + i2)
}
fn main() {
let x = add("123", "not a number");
println!("x = {:?}", x);
}
```
```sh
cargo run --example basic_without_wherr
```
```
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`:
```rust
// The add() function is as previously defined and is omitted here for clarity.
fn main() {
let x = add("123", "not a number").unwrap();
println!("x = {:?}", x);
}
```
```sh
cargo run --example unwrap_without_wherr
```
```
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:
```rust
let x = add("123", "not a number").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`:
```rust
use wherr::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.
#[wherr]
fn add(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> {
let radix = 10;
let i1 = i64::from_str_radix(s1, radix)?;
let i2 = i64::from_str_radix(s2, radix)?;
Ok(i1 + i2)
}
fn main() {
let x = add("123", "not a number");
println!("x = {:?}", x);
}
```
```sh
cargo run --example basic_with_wherr
```
```
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:
```rust
let i2 = i64::from_str_radix(s2, radix)?;
```
`examples/unwrap_with_wherr.rs`:
```rust
use wherr::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.
#[wherr]
fn add(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> {
let radix = 10;
let i1 = i64::from_str_radix(s1, radix)?;
let i2 = i64::from_str_radix(s2, radix)?;
Ok(i1 + i2)
}
fn main() {
let x = add("123", "not a number").unwrap();
println!("x = {:?}", x);
}
```
```sh
cargo run --example unwrap_with_wherr
```
```
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:
```rust
let i2 = i64::from_str_radix(s2, radix)?;
```
And the line **wherr/examples/unwrap_with_wherr.rs:15** corresponds to:
```rust
let x = add("123", "not a number").unwrap();
```
Here is the diff (additional newlines added for clarity):
```diff
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`:
```rust
match add("123", "not a number") {
Ok(sum) => {
println!("sum = {}", sum);
}
Err(e) => {
if let Some(wherr) = e.downcast_ref::<wherr::Wherr>() {
println!(
"Error at file: '{}', line: {}. Original error: {}",
wherr.file, wherr.line, wherr.inner
);
} else {
println!("Unexpected error: {}", e);
}
}
}
```
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.
```rust
pub struct Wherr {
inner: Box<dyn std::error::Error>,
file: &'static str,
line: u32,
}
```
Methods:
- `new(err: Box<dyn std::error::Error>, file: &'static str, line: u32) -> Self`: Creates a new `Wherr` error 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.
```rust
pub fn wherrapper<T, E>(
result: Result<T, E>,
file: &'static str,
line: u32,
) -> Result<T, Box<dyn std::error::Error>>
```
### `wherr` procedural macro
A procedural macro that auto-wraps errors (using the `?` operator) inside
a function with file and line number details.
```rust
#[wherr]
fn some_function() -> Result<(), Box<dyn std::error::Error>> { /* ... */ }
```
## 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.