Skip to content

iex-rs/iex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

93 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Idiomatic exceptions for Rust

Crates.io Version docs.rs

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"));
}