lithium/
lib.rs

1//! Lightweight exceptions.
2//!
3//! Lithium provides a custom exception mechanism as an alternative to Rust panics. Compared to Rust
4//! panics, this mechanism is allocation-free, avoids indirections and RTTI, and is hence faster, if
5//! less applicable.
6//!
7//! On nightly, Lithium is more than 2x faster than Rust panics on common `Result`-like usecases.
8//! See the [benchmark](https://github.com/iex-rs/lithium/blob/master/benches/bench.rs).
9//!
10//!
11//! # Usage
12//!
13//! Throw an exception with [`throw`], catch it with [`catch`] or the more low-level [`intercept`].
14//! Unlike with Rust panics, non-[`Send`] and non-`'static` types can be used soundly.
15//!
16//! Using the `panic = "abort"` strategy breaks Lithium; avoid doing that.
17//!
18//! For interop, all crates that depend on Lithium need to use the same version:
19//!
20//! ```toml
21//! [dependencies]
22//! lithium = "1"
23//! ```
24//!
25//! If you break either of these two requirements, cargo will scream at you.
26//!
27//!
28//! # Platform support
29//!
30//! On stable Rust, Lithium uses the built-in panic mechanism, tweaking it to increase performance
31//! just a little bit.
32//!
33//! On nightly Rust, Lithium uses a custom mechanism on the following targets:
34//!
35//! |Target             |Implementation |Performance                                  |
36//! |-------------------|---------------|---------------------------------------------|
37//! |Linux, macOS       |Itanium EH ABI |2.5x faster than panics                      |
38//! |Windows (MSVC ABI) |SEH            |1.5x faster than panics                      |
39//! |Windows (GNU ABI)  |Itanium EH ABI |2.5x faster than panics, but slower than MSVC|
40//! |Emscripten (old EH)|C++ exceptions |2x faster than panics                        |
41//! |Emscripten (new EH)|Wasm exceptions|2.5x faster than panics                      |
42//! |WASI               |Itanium EH ABI |2.5x faster than panics                      |
43//!
44//! Lithium strives to support all targets that Rust panics support. If Lithium does not work
45//! correctly on such a target, please [open an issue](https://github.com/iex-rs/lithium/issues/).
46//!
47//! On nightly, Lithium can work without `std` on certain platforms that expose native thread
48//! locals and link in an Itanium-style unwinder. Such situations are best handled on a case-by-case
49//! basis: [open an issue](https://github.com/iex-rs/lithium/issues/) if you would like to see
50//! support for a certain `std`-less target.
51//!
52//!
53//! # Safety
54//!
55//! Exceptions lack dynamic typing information. For soundness, the thrown and caught types must
56//! match exactly. Note that the functions are generic, and if the type is inferred wrong, UB will
57//! happen. Use turbofish to avoid this pitfall.
58//!
59//! The matching types requirement only applies to exceptions that aren't caught inside the
60//! [`catch`]/[`intercept`] callback. For example, this is sound:
61//!
62//! ```rust
63//! use lithium::{catch, throw};
64//!
65//! struct A;
66//! struct B;
67//!
68//! unsafe {
69//!     let _ = catch::<_, A>(|| {
70//!         let _ = catch::<_, B>(|| throw(B));
71//!         throw(A);
72//!     });
73//! }
74//! ```
75//!
76//! The responsibility of upholding this safety requirement is split between the throwing and the
77//! catching functions. All throwing functions must be `unsafe`, listing "only caught by type `E`"
78//! as a safety requirement. All catching functions that take a user-supplied callback must be
79//! `unsafe` too, listing "callback only throws type `E`" as a safety requirement.
80//!
81//! Although seemingly redundant, this enables safe abstractions over exceptions when both the
82//! throwing and the catching functions are provided by one crate. As long as the exception types
83//! used by the crate match, all safe user-supplied callbacks are sound to call, because safe
84//! callbacks can only interact with exceptions in an isolated manner.
85
86#![no_std]
87#![cfg_attr(all(thread_local = "attribute"), feature(thread_local))]
88#![cfg_attr(
89    any(backend = "itanium", backend = "seh", backend = "emscripten"),
90    expect(
91        internal_features,
92        reason = "Can't do anything about core::intrinsics::catch_unwind yet",
93    )
94)]
95#![cfg_attr(
96    any(backend = "itanium", backend = "seh", backend = "emscripten"),
97    feature(core_intrinsics, rustc_attrs)
98)]
99#![cfg_attr(backend = "seh", feature(fn_ptr_trait, std_internals))]
100#![cfg_attr(
101    all(backend = "itanium", target_arch = "wasm32"),
102    feature(wasm_exception_handling_intrinsics)
103)]
104#![deny(unsafe_op_in_unsafe_fn)]
105#![warn(
106    clippy::cargo,
107    clippy::pedantic,
108    clippy::alloc_instead_of_core,
109    clippy::allow_attributes_without_reason,
110    clippy::arithmetic_side_effects,
111    clippy::as_underscore,
112    clippy::assertions_on_result_states,
113    clippy::clone_on_ref_ptr,
114    clippy::decimal_literal_representation,
115    clippy::default_numeric_fallback,
116    clippy::deref_by_slicing,
117    clippy::else_if_without_else,
118    clippy::empty_drop,
119    clippy::empty_enum_variants_with_brackets,
120    clippy::empty_structs_with_brackets,
121    clippy::exhaustive_enums,
122    clippy::exhaustive_structs,
123    clippy::fn_to_numeric_cast_any,
124    clippy::format_push_string,
125    clippy::infinite_loop,
126    clippy::inline_asm_x86_att_syntax,
127    clippy::mem_forget, // use ManuallyDrop instead
128    clippy::missing_assert_message,
129    clippy::missing_const_for_fn,
130    clippy::missing_inline_in_public_items,
131    clippy::mixed_read_write_in_expression,
132    clippy::multiple_unsafe_ops_per_block,
133    clippy::mutex_atomic,
134    clippy::needless_raw_strings,
135    clippy::pub_without_shorthand,
136    clippy::rc_buffer,
137    clippy::rc_mutex,
138    clippy::redundant_type_annotations,
139    clippy::rest_pat_in_fully_bound_structs,
140    clippy::same_name_method,
141    clippy::self_named_module_files,
142    clippy::semicolon_inside_block,
143    clippy::separated_literal_suffix,
144    clippy::shadow_unrelated,
145    clippy::std_instead_of_alloc,
146    clippy::std_instead_of_core,
147    clippy::string_lit_chars_any,
148    clippy::string_to_string,
149    clippy::tests_outside_test_module,
150    clippy::try_err,
151    clippy::undocumented_unsafe_blocks,
152    clippy::unnecessary_safety_comment,
153    clippy::unnecessary_safety_doc,
154    clippy::unnecessary_self_imports,
155    clippy::unneeded_field_pattern,
156    clippy::unused_result_ok,
157    clippy::wildcard_enum_match_arm,
158)]
159#![allow(
160    clippy::inline_always,
161    reason = "I'm not an idiot, this is a result of benchmarking/profiling"
162)]
163
164#[cfg(panic = "abort")]
165compile_error!("Using Lithium with panic = \"abort\" is unsupported");
166
167#[cfg(any(abort = "std", backend = "panic", thread_local = "std", test))]
168extern crate std;
169
170extern crate alloc;
171
172mod api;
173mod backend;
174
175#[cfg(any(backend = "itanium", backend = "emscripten", backend = "panic"))]
176mod heterogeneous_stack;
177#[cfg(any(backend = "itanium", backend = "emscripten", backend = "panic"))]
178mod stacked_exceptions;
179
180#[cfg(any(backend = "itanium", backend = "seh", backend = "emscripten"))]
181mod intrinsic;
182
183pub use api::{catch, intercept, throw, InFlightException};
184
185/// Abort the process with a message.
186///
187/// If `std` is available, this also outputs a message to stderr before aborting.
188#[cfg(any(backend = "itanium", backend = "seh", backend = "emscripten"))]
189#[cold]
190#[inline(never)]
191fn abort(message: &str) -> ! {
192    #[cfg(abort = "std")]
193    {
194        use std::io::Write;
195        let _ = std::io::stderr().write_all(message.as_bytes());
196        std::process::abort();
197    }
198
199    // This is a nightly-only method, but all three backends this is enabled under require nightly
200    // anyway, so this is no big deal.
201    #[cfg(not(abort = "std"))]
202    {
203        let _ = message;
204        core::intrinsics::abort();
205    }
206}