2 unstable releases
0.2.0 | Jul 28, 2024 |
---|---|
0.1.0 | Jul 22, 2024 |
#1838 in Rust patterns
51 downloads per month
45KB
570 lines
Idiomatic exceptions for Rust
Speed up the happy path of your Result
-based functions by seamlessly using exceptions for error
propagation.
Crash course
Stick #[iex]
on all the functions that return Result
to make them return an efficiently
propagatable #[iex] Result
, apply ?
just like usual, and occasionally call .into_result()
when you need a real Result
. It's that intuitive.
Compared to an algebraic Result
, #[iex] Result
is asymmetric: it sacrifices the performance of
error handling, and in return:
- Gets rid of branching in the happy path,
- Reduces memory usage by never explicitly storing the error or the enum discriminant,
- Enables the compiler to use registers instead of memory when wrapping small objects in
Ok
, - Cleanly separates the happy and unhappy paths in the machine code, resulting in better instruction locality.
Benchmark
As a demonstration, we have rewritten serde and serde_json to use #[iex]
in the
deserialization path and used the Rust JSON Benchmark to compare performance. These are the
results:
Speed (MB/s) | canada |
citm_catalog |
twitter |
|||
---|---|---|---|---|---|---|
DOM | struct | DOM | struct | DOM | struct | |
Result |
296.2 | 439.0 | 392.4 | 876.8 | 274.8 | 536.4 |
#[iex] Result |
294.8 | 537.0 | 400.6 | 940.6 | 303.8 | 568.8 |
Performance increase | -0.5% | +22% | +2% | +7% | +11% | +6% |
The data is averaged between 5 runs. The repositories for data reproduction are published on GitHub.
Example
use iex::{iex, Outcome};
#[iex]
fn checked_divide(a: u32, b: u32) -> Result<u32, &'static str> {
if b == 0 {
// Actually raises a custom panic
Err("Cannot divide by zero")
} else {
// Actually returns a / b directly
Ok(a / b)
}
}
#[iex]
fn checked_divide_by_many_numbers(a: u32, bs: &[u32]) -> Result<Vec<u32>, &'static str> {
let mut results = Vec::new();
for &b in bs {
// Actually lets the panic bubble
results.push(checked_divide(a, b)?);
}
Ok(results)
}
fn main() {
// Actually catches the panic
let result = checked_divide_by_many_numbers(5, &[1, 2, 3, 0]).into_result();
assert_eq!(result, Err("Cannot divide by zero"));
}
Dependencies
~0.6–1MB
~23K SLoC