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}