Expand description
Idiomatic exceptions.
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 |
282.4 | 404.2 | 363.8 | 907.8 | 301.2 | 612.4 |
#[iex] Result |
282.4 | 565.0 | 439.4 | 1025.4 | 317.6 | 657.8 |
Performance increase | 0% | +40% | +21% | +13% | +5% | +7% |
The data is averaged between 5 runs. The repositories for data reproduction are published on GitHub.
This benchmark only measures the happy path. When triggered, exceptions are significantly slower
than algebraic Result
s. However, it is important to recognize that realistic programs
perform actions other than throwing errors, and the slowness of the error path is offset by the
increased speed of the happy path. For JSON parsing in particular, the break-even point is 1
error per 30-100k bytes parsed, depending on the data.
Note that just blindly slapping #[iex]
onto every single function might not
increase your performance at best and will decrease it at worst. Like with every other
optimization, it is critical to profile code and measure performance on realistic data.
§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"));
}
§All you need to know
Functions marked #[iex]
are supposed to return a Result<T, E>
in their
definition. The macro rewrites them to return an opaque type #[iex] Result<T, E>
instead. This
type implements Outcome
, so you can call methods like map_err
, but
other than that, you must immediately propagate the error via ?
.
Alternatively, you can cast it to a Result
via .into_result()
.
This is the only way to avoid immediate propagation.
Doing anything else to the return value, e.g. storing it in a variable and using it later will
not cause UB, but will not work the way you think either. If you want to swallow the error, use
let _ = func().into_result();
instead.
Directly returning an #[iex] Result
(obtained from a function call) from another
#[iex]
function also works, provided that it’s the only return
statement in the
function. Use Ok(..?)
if there are multiple returns.
#[iex]
works on methods. If applied to a function in an impl Trait for Type
block, the corresponding function in the trait Trait
block should also be marked with
#[iex]
. Such traits are not object-safe, unless the method is restricted to
where Self: Sized
(open an issue if you want me to spend time developing a workaround).
Modules§
Macros§
- try_
block - Try block.
Traits§
Attribute Macros§
- iex
- Use unwinding for error propagation.