iex-rs/iex

Idiomatic exceptions for Rust

Rust

103

93 commits

updated Jul 22, 2025

See the code

README

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)canadacitm_catalogtwitter
DOMstructDOMstructDOMstruct
Result296.2439.0392.4876.8274.8536.4
#[iex] Result294.8537.0400.6940.6303.8568.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"));
}

Contributors

purplesyringa

89 commits

Licenser

3 commits

q60

1 commits

iex-rs/iex

Idiomatic exceptions for Rust

Rust

103

93 commits

updated Jul 22, 2025

See the code

README

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)canadacitm_catalogtwitter
DOMstructDOMstructDOMstruct
Result296.2439.0392.4876.8274.8536.4
#[iex] Result294.8537.0400.6940.6303.8568.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"));
}

Contributors

purplesyringa

89 commits

Licenser

3 commits

q60

1 commits

Languages

Rust

100.0%