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}