iex/
lib.rs

1//! Idiomatic exceptions.
2//!
3//! Speed up the happy path of your [`Result`]-based functions by seamlessly using exceptions for
4//! error propagation.
5//!
6//! # Crash course
7//!
8//! Stick [`#[iex]`](macro@iex) on all the functions that return [`Result`] to make them return an
9//! efficiently propagatable `#[iex] Result`, apply `?` just like usual, and occasionally call
10//! [`.into_result()`](Outcome::into_result) when you need a real [`Result`]. It's that intuitive.
11//!
12//! Compared to an algebraic [`Result`], `#[iex] Result` is asymmetric: it sacrifices the
13//! performance of error handling, and in return:
14//! - Gets rid of branching in the happy path,
15//! - Reduces memory usage by never explicitly storing the error or the enum discriminant,
16//! - Enables the compiler to use registers instead of memory when wrapping small objects in [`Ok`],
17//! - Cleanly separates the happy and unhappy paths in the machine code, resulting in better
18//!   instruction locality.
19//!
20//! # Benchmark
21//!
22//! As a demonstration, we have rewritten [serde](https://serde.rs) and
23//! [serde_json](https://crates.io/crates/serde_json) to use `#[iex]` in the deserialization path
24//! and used the [Rust JSON Benchmark](https://github.com/serde-rs/json-benchmark) to compare
25//! performance. These are the results:
26//!
27//! <table width="100%">
28//!     <thead>
29//!         <tr>
30//!             <td rowspan="2">Speed (MB/s)</td>
31//!             <th colspan="2"><code>canada</code></th>
32//!             <th colspan="2"><code>citm_catalog</code></th>
33//!             <th colspan="2"><code>twitter</code></th>
34//!         </tr>
35//!         <tr>
36//!             <th>DOM</th>
37//!             <th>struct</th>
38//!             <th>DOM</th>
39//!             <th>struct</th>
40//!             <th>DOM</th>
41//!             <th>struct</th>
42//!         </tr>
43//!     </thead>
44//!     <tbody>
45//!         <tr>
46//!             <td><a href="https://doc.rust-lang.org/nightly/core/result/enum.Result.html"><code>Result</code></a></td>
47//!             <td align="center">282.4</td>
48//!             <td align="center">404.2</td>
49//!             <td align="center">363.8</td>
50//!             <td align="center">907.8</td>
51//!             <td align="center">301.2</td>
52//!             <td align="center">612.4</td>
53//!         </tr>
54//!         <tr>
55//!             <td><code>#[iex] Result</code></td>
56//!             <td align="center">282.4</td>
57//!             <td align="center">565.0</td>
58//!             <td align="center">439.4</td>
59//!             <td align="center">1025.4</td>
60//!             <td align="center">317.6</td>
61//!             <td align="center">657.8</td>
62//!         </tr>
63//!         <tr>
64//!             <td>Performance increase</td>
65//!             <td align="center">0%</td>
66//!             <td align="center">+40%</td>
67//!             <td align="center">+21%</td>
68//!             <td align="center">+13%</td>
69//!             <td align="center">+5%</td>
70//!             <td align="center">+7%</td>
71//!         </tr>
72//!     </tbody>
73//! </table>
74//!
75//! The data is averaged between 5 runs. The repositories for data reproduction are published
76//! [on GitHub](https://github.com/orgs/iex-rs/repositories).
77//!
78//! This benchmark only measures the happy path. When triggered, exceptions are significantly slower
79//! than algebraic [`Result`]s. However, it is important to recognize that realistic programs
80//! perform actions other than throwing errors, and the slowness of the error path is offset by the
81//! increased speed of the happy path. For JSON parsing in particular, the break-even point is 1
82//! error per 30-100k bytes parsed, depending on the data.
83//!
84//! Note that just blindly slapping [`#[iex]`](macro@iex) onto every single function might not
85//! increase your performance at best and will decrease it at worst. Like with every other
86//! optimization, it is critical to profile code and measure performance on realistic data.
87//!
88//! # Example
89//!
90//! ```
91//! use iex::{iex, Outcome};
92//!
93//! #[iex]
94//! fn checked_divide(a: u32, b: u32) -> Result<u32, &'static str> {
95//!     if b == 0 {
96//!         // Actually raises a custom panic
97//!         Err("Cannot divide by zero")
98//!     } else {
99//!         // Actually returns a / b directly
100//!         Ok(a / b)
101//!     }
102//! }
103//!
104//! #[iex]
105//! fn checked_divide_by_many_numbers(a: u32, bs: &[u32]) -> Result<Vec<u32>, &'static str> {
106//!     let mut results = Vec::new();
107//!     for &b in bs {
108//!         // Actually lets the panic bubble
109//!         results.push(checked_divide(a, b)?);
110//!     }
111//!     Ok(results)
112//! }
113//!
114//! fn main() {
115//!     // Actually catches the panic
116//!     let result = checked_divide_by_many_numbers(5, &[1, 2, 3, 0]).into_result();
117//!     assert_eq!(result, Err("Cannot divide by zero"));
118//! }
119//! ```
120//!
121//! # All you need to know
122//!
123//! Functions marked [`#[iex]`](macro@iex) are supposed to return a [`Result<T, E>`] in their
124//! definition. The macro rewrites them to return an opaque type `#[iex] Result<T, E>` instead. This
125//! type implements [`Outcome`], so you can call methods like [`map_err`](Outcome::map_err), but
126//! other than that, you must immediately propagate the error via `?`.
127//!
128//! Alternatively, you can cast it to a [`Result`] via [`.into_result()`](Outcome::into_result).
129//! This is the only way to avoid immediate propagation.
130//!
131//! Doing anything else to the return value, e.g. storing it in a variable and using it later will
132//! not cause UB, but will not work the way you think either. If you want to swallow the error, use
133//! `let _ = func().into_result();` instead.
134//!
135//! Directly returning an `#[iex] Result` (obtained from a function call) from another
136//! [`#[iex]`](macro@iex) function also works, provided that it's the only `return` statement in the
137//! function. Use `Ok(..?)` if there are multiple returns.
138//!
139//! [`#[iex]`](macro@iex) works on methods. If applied to a function in an `impl Trait for Type`
140//! block, the corresponding function in the `trait Trait` block should also be marked with
141//! [`#[iex]`](macro@iex). Such traits are not object-safe, unless the method is restricted to
142//! `where Self: Sized` (open an issue if you want me to spend time developing a workaround).
143
144#![cfg_attr(doc, feature(doc_auto_cfg))]
145
146mod macros;
147pub use macros::{iex, try_block};
148
149use std::cell::UnsafeCell;
150
151mod exception;
152use exception::Exception;
153
154mod outcome;
155pub use outcome::Outcome;
156
157#[cfg(feature = "anyhow")]
158mod anyhow_compat;
159#[cfg(feature = "anyhow")]
160pub use anyhow_compat::Context;
161
162#[cfg(not(feature = "anyhow"))]
163pub trait Context<T, E> {}
164#[cfg(not(feature = "anyhow"))]
165impl<T, E> Context<T, E> for Result<T, E> {}
166#[cfg(not(feature = "anyhow"))]
167impl<T, E, Func: iex_result::CallWithMarker<T, E>> Context<T, E> for imp::IexResult<T, E, Func> {}
168#[cfg(not(feature = "anyhow"))]
169impl<T> Context<T, std::convert::Infallible> for Option<T> {}
170
171mod iex_result;
172mod result;
173
174mod exception_mapper;
175mod forward;
176mod marker;
177
178pub mod example;
179
180struct IexPanic;
181
182thread_local! {
183    static EXCEPTION: UnsafeCell<Exception> = const { UnsafeCell::new(Exception::new()) };
184}
185
186#[doc(hidden)]
187pub mod imp {
188    use super::*;
189    pub use exception_mapper::ExceptionMapper;
190    pub use fix_hidden_lifetime_bug;
191    pub use forward::_IexForward;
192    pub use iex_result::IexResult;
193    pub use marker::Marker;
194    pub struct NoCopy;
195}
196
197extern crate self as iex;