fast_assert/lib.rs
1#![doc = include_str!("../README.md")]
2
3/// A reimplementation of assert! that uses a closure to defer all
4/// panic-related work to the cold path.
5#[macro_export]
6macro_rules! fast_assert {
7 // Rule 1: Handles calls with only a condition, like my_assert!(x == y).
8 // It also accepts an optional trailing comma, like my_assert!(x == y,).
9 ($cond:expr $(,)?) => {
10 if !$cond {
11 // If the condition is false, panic with a default message.
12 // The stringify! macro converts the expression `$cond` into a string literal,
13 // so the error message includes the exact code that failed.
14 $crate::cold::assert_failed(|| {
15 panic!("assertion failed: {}", stringify!($cond));
16 });
17 }
18 };
19 // Rule 2: Handles calls with a condition and a custom message,
20 // like my_assert!(x == y, "x should be equal to y, but was {}", x).
21 ($cond:expr, $($arg:tt)+) => {
22 if !$cond {
23 // We pass a closure to the cold function.
24 // No code inside this closure will be generated in the hot path.
25 $crate::cold::assert_failed(|| {
26 panic!($($arg)+);
27 });
28 }
29 };
30}
31
32/// Private implementation detail.
33/// `pub` is required to make macros work from other crates, so stick #[doc(hidden)] on it.
34#[doc(hidden)]
35pub mod cold {
36 /// This function is marked as `#[cold]` to hint to the compiler that it's
37 /// rarely executed. The compiler uses this to optimize the call site,
38 /// keeping the "hot path" (where the assertion succeeds) as lean as possible.
39 ///
40 /// This function is generic over a closure `F`.
41 /// `F: FnOnce()` means it accepts any closure that can be called once
42 /// and takes no arguments.
43 ///
44 /// The panic logic is provided by the caller via this closure.
45 ///
46 /// This doesn't need #[inline] because the function is generic
47 /// and will be separately instantiated for each call site.
48 #[cold]
49 #[track_caller]
50 pub fn assert_failed<F>(msg_fn: F)
51 where
52 F: FnOnce(),
53 {
54 // We simply call the closure, which contains the panic!.
55 msg_fn();
56 }
57}
58
59/// We only run basic sanity checks here. The really interesting tests are in a separate crate in this workspace.
60/// This is because getting macros to work when instantiated in the same file is easy,
61/// but getting them to work across crates is harder.
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn holds() {
68 fast_assert!(0 < 100);
69 }
70
71 #[test]
72 #[should_panic]
73 fn fails() {
74 fast_assert!(100 < 0);
75 }
76
77 #[test]
78 fn holds_custom_message() {
79 let x = 0;
80 let y = 100;
81 fast_assert!(x < y, "x ({}) should be less than y ({})", x, y);
82 }
83
84 #[test]
85 #[should_panic]
86 fn fails_custom_message() {
87 let x = 100;
88 let y = 0;
89 fast_assert!(x < y, "x ({}) should be less than y ({})", x, y);
90 }
91}