assert_unchecked/
lib.rs

1//! Unsafe assertions that allow for optimizations in release mode. All of these
2//! assertions if incorrect will invoke *undefined behavior* (UB) when
3//! `debug_assertions` are disabled, so all usage of these macros must ensure
4//! that they will truly never fail.
5#![no_std]
6
7/// Asserts that a boolean expression is `true` at runtime.
8///
9/// In builds with `debug-assertions` enabled, this will function equivalent to
10/// [`assert`]. However, in an optimized build without `debug_assertions`
11/// enabled, this assertion serves as an optimization hint; the boolean
12/// expression itself will likely not appear in the generated code, but instead
13/// will be assumed in a way that allows for optimizing the surrounding code.
14///
15/// # Safety
16///
17/// In release mode, the assertion failing is completely *undefined behavior*
18/// (UB). Since the compiler assumes that all UB must never happen, it may use
19/// the assumption that this assertion is true to optimize other sections of the
20/// code.
21///
22/// If this assumption turns out to be wrong, i.e. the assertion can fail in
23/// practice, the compiler will apply the wrong optimization strategy, and
24/// may sometimes even corrupt seemingly unrelated code, causing
25/// difficult-to-debug problems.
26///
27/// Use this function only when you can prove that the assertion will never be
28/// false. Otherwise, consider just using [`assert`], or if assertions are
29/// undesired in optimized code, use [`debug_assert`].
30/// # Example
31///
32/// ```
33/// use assert_unchecked::assert_unchecked;
34/// fn copy(from_arr: &[u8], to_arr: &mut [u8]) {
35///     assert_eq!(from_arr.len(), to_arr.len());
36///     for i in 0..to_arr.len() {
37///         // SAFETY: bounds of to_arr is checked outside of loop
38///         // Without this line, the compiler isn't smart enough to remove the bounds check
39///         unsafe { assert_unchecked!(i <= to_arr.len()) };
40///         to_arr[i] = from_arr[i];
41///     }
42/// }
43/// ```
44#[macro_export]
45macro_rules! assert_unchecked {
46    ($cond:expr) => (assert_unchecked!($cond,));
47    ($expr:expr, $($arg:tt)*) => ({
48        #[cfg(debug_assertions)]
49        {
50            unsafe fn __needs_unsafe(){}
51            __needs_unsafe();
52            assert!($expr, $($arg)*);
53        }
54        #[cfg(not(debug_assertions))]
55        {
56            if !($expr) { core::hint::unreachable_unchecked() }
57        }
58    })
59}
60
61/// Asserts that two expressions are equal to each other (using [`PartialEq`]).
62///
63/// In builds with `debug-assertions` enabled, this will function equivalent to
64/// [`assert_eq`]. However, in an optimized build without `debug_assertions`
65/// enabled, this assertion serves as an optimization hint; the equality check
66/// itself will likely not appear in the generated code, but instead will be
67/// assumed in a way that allows for optimizing the surrounding code.
68///
69/// # Safety
70///
71/// In release mode, the assertion failing is completely *undefined behavior*
72/// (UB). Since the compiler assumes that all UB must never happen, it may use
73/// the assumption that this assertion is true to optimize other sections of the
74/// code.
75///
76/// If this assumption turns out to be wrong, i.e. the assertion can fail in
77/// practice, the compiler will apply the wrong optimization strategy, and
78/// may sometimes even corrupt seemingly unrelated code, causing
79/// difficult-to-debug problems.
80///
81/// Use this function only when you can prove that the assertion will never be
82/// false. Otherwise, consider just using [`assert_eq`], or if assertions are
83/// undesired in optimized code, use [`debug_assert_eq`].
84///
85/// # Example
86///
87/// ```
88/// use assert_unchecked::assert_eq_unchecked;
89/// fn get_last(len: usize) -> usize {
90///     if len == 0 {
91///         return 0;
92///     }
93///     let mut v = vec![0];
94///     for i in 1..len {
95///         v.push(i)
96///     }
97///     // SAFETY: `len` elements have been added to v at this point
98///     // Without this line, the compiler isn't smart enough to remove the bounds check
99///     unsafe { assert_eq_unchecked!(len, v.len()) };
100///     v[len - 1]
101/// }
102/// ```
103#[macro_export]
104macro_rules! assert_eq_unchecked {
105    ($left:expr, $right:expr) => (assert_eq_unchecked!($left, $right,));
106    ($left:expr, $right:expr, $($arg:tt)*) => ({
107        #[cfg(debug_assertions)]
108        {
109            unsafe fn __needs_unsafe(){}
110            __needs_unsafe();
111            assert_eq!($left, $right, $($arg)*);
112        }
113        #[cfg(not(debug_assertions))]
114        {
115            if !($left == $right) {
116                core::hint::unreachable_unchecked()
117            }
118        }
119    })
120}
121
122/// Asserts that two expressions are not equal to each other (using
123/// [`PartialEq`]).
124///
125/// In builds with `debug-assertions` enabled, this will function equivalent to
126/// [`assert_ne`]. However, in an optimized build without `debug_assertions`
127/// enabled, this assertion serves as an optimization hint; the inequality check
128/// itself will likely not appear in the generated code, but instead will be
129/// assumed in a way that allows for optimizing the surrounding code.
130///
131/// # Safety
132///
133/// In release mode, the assertion failing is completely *undefined behavior*
134/// (UB). Since the compiler assumes that all UB must never happen, it may use
135/// the assumption that this assertion is true to optimize other sections of the
136/// code.
137///
138/// If this assumption turns out to be wrong, i.e. the assertion can fail in
139/// practice, the compiler will apply the wrong optimization strategy, and
140/// may sometimes even corrupt seemingly unrelated code, causing
141/// difficult-to-debug problems.
142///
143/// Use this function only when you can prove that the assertion will never be
144/// false. Otherwise, consider just using [`assert_ne`], or if assertions are
145/// undesired in optimized code, use [`debug_assert_ne`].
146///
147/// # Example
148///
149/// ```
150/// use assert_unchecked::{assert_unchecked, assert_ne_unchecked};
151/// // Modifies `a[0]` and `a[delta]`, and then returns `a[0]`.
152/// // delta must be non-zero and delta < a.len().
153/// // This also means that all bounds checks can be removed.
154/// unsafe fn modify_start_and_delta(a: &mut [u8], delta: usize) -> u8 {
155///     // SAFETY: requirements are invariants of the unsafe function.
156///     assert_unchecked!(delta < a.len());
157///     // With this assertion, we know that a[delta] does not modify a[0],
158///     // which means the function can optimize the return value to always be 0.
159///     assert_ne_unchecked!(delta, 0);
160///     a[0] = 0;
161///     a[delta] = 1;
162///     a[0]
163/// }
164/// ```
165#[macro_export]
166macro_rules! assert_ne_unchecked {
167    ($left:expr, $right:expr) => (assert_ne_unchecked!($left, $right,));
168    ($left:expr, $right:expr, $($arg:tt)*) => ({
169        #[cfg(debug_assertions)]
170        {
171            unsafe fn __needs_unsafe(){}
172            __needs_unsafe();
173            assert_ne!($left, $right, $($arg)*);
174        }
175        #[cfg(not(debug_assertions))]
176        {
177            if $left == $right {
178                core::hint::unreachable_unchecked()
179            }
180        }
181    })
182}
183
184/// Equivalent to the [`unreachable!`] macro in builds with `debug_assertions`
185/// on, and otherwise calls [`core::hint::unreachable_unchecked`].
186///
187/// # Safety
188///
189/// In release mode, reaching this function is completely *undefined behavior*
190/// (UB). For more details, see the documentation for [`unreachable_unchecked`].
191///
192/// Use this function only when you can prove that the code will never call it.
193/// Otherwise, consider using the [`unreachable!`] macro, which does not allow
194/// optimizations but will panic when executed.
195///
196/// # Example
197///
198/// ```
199/// use assert_unchecked::unreachable_unchecked;
200/// fn div_1(a: u32, b: u32) -> u32 {
201///     // `b.saturating_add(1)` is always positive (not zero),
202///     // hence `checked_div` will never return `None`.
203///     // Therefore, the else branch is unreachable.
204///     a.checked_div(b.saturating_add(1))
205///         .unwrap_or_else(|| unsafe { unreachable_unchecked!("division by zero isn't possible") })
206/// }
207///
208/// assert_eq!(div_1(7, 0), 7);
209/// assert_eq!(div_1(9, 1), 4);
210/// assert_eq!(div_1(11, u32::MAX), 0);
211/// ```
212#[macro_export]
213macro_rules! unreachable_unchecked {
214    ($($arg:tt)*) => ({
215        #[cfg(debug_assertions)]
216        {
217            unsafe fn __needs_unsafe(){}
218            __needs_unsafe();
219            unreachable!($($arg)*);
220        }
221        #[cfg(not(debug_assertions))]
222        {
223            core::hint::unreachable_unchecked()
224        }
225    })
226}
227
228#[cfg(test)]
229mod debug_assertion_tests {
230    /// For tests that panic, we only test them when debug_assertions are on,
231    /// as otherwise we'd be invoking UB.
232
233    // Ensures that the expression isn't emplaced twice
234    #[derive(Eq, PartialEq, Debug)]
235    struct NoCopy(usize);
236
237    #[test]
238    fn test_assert_success() {
239        unsafe {
240            assert_unchecked!(NoCopy(0) == NoCopy(0));
241            assert_unchecked!(NoCopy(1) == NoCopy(1),);
242            assert_unchecked!(NoCopy(2) == NoCopy(2), "message won't appear");
243            assert_unchecked!(NoCopy(3) == NoCopy(3), "{} won't {}", "message", "appear");
244        }
245    }
246
247    #[test]
248    #[cfg(debug_assertions)]
249    #[should_panic(expected = "assertion failed: NoCopy(0) == NoCopy(1)")]
250    fn test_assert_fail_no_message() {
251        unsafe { assert_unchecked!(NoCopy(0) == NoCopy(1)) }
252    }
253
254    #[test]
255    #[cfg(debug_assertions)]
256    #[should_panic(expected = "assertion message")]
257    fn test_assert_fail_message() {
258        unsafe { assert_unchecked!(NoCopy(0) == NoCopy(1), "assertion message") }
259    }
260
261    #[test]
262    #[cfg(debug_assertions)]
263    #[should_panic(expected = "assertion message")]
264    fn test_assert_fail_message_format() {
265        unsafe { assert_unchecked!(NoCopy(0) == NoCopy(1), "assertion {}", "message") }
266    }
267
268    #[test]
269    fn test_assert_eq_success() {
270        unsafe {
271            assert_eq_unchecked!(NoCopy(0), NoCopy(0));
272            assert_eq_unchecked!(NoCopy(1), NoCopy(1),);
273            assert_eq_unchecked!(NoCopy(2), NoCopy(2), "message won't appear");
274            assert_eq_unchecked!(NoCopy(3), NoCopy(3), "{} won't {}", "message", "appear");
275        }
276    }
277
278    #[test]
279    #[cfg(debug_assertions)]
280    #[should_panic(expected = "assertion failed: `(left == right)`")]
281    fn test_assert_eq_fail_no_message() {
282        unsafe { assert_eq_unchecked!(NoCopy(0), NoCopy(1)) }
283    }
284
285    #[test]
286    #[cfg(debug_assertions)]
287    #[should_panic(expected = "assertion message")]
288    fn test_assert_eq_fail_message() {
289        unsafe { assert_eq_unchecked!(NoCopy(0), NoCopy(1), "assertion message") }
290    }
291
292    #[test]
293    #[cfg(debug_assertions)]
294    #[should_panic(expected = "assertion message")]
295    fn test_assert_eq_fail_message_format() {
296        unsafe { assert_eq_unchecked!(NoCopy(0), NoCopy(1), "assertion {}", "message") }
297    }
298
299    #[test]
300    fn test_assert_ne_success() {
301        unsafe {
302            assert_ne_unchecked!(NoCopy(0), NoCopy(1));
303            assert_ne_unchecked!(NoCopy(1), NoCopy(2),);
304            assert_ne_unchecked!(NoCopy(2), NoCopy(3), "message won't appear");
305            assert_ne_unchecked!(NoCopy(3), NoCopy(4), "{} won't {}", "message", "appear");
306        }
307    }
308
309    #[test]
310    #[cfg(debug_assertions)]
311    #[should_panic(expected = "assertion failed: `(left != right)`")]
312    fn test_assert_ne_fail_no_message() {
313        unsafe { assert_ne_unchecked!(NoCopy(0), NoCopy(0)) }
314    }
315
316    #[test]
317    #[cfg(debug_assertions)]
318    #[should_panic(expected = "assertion message")]
319    fn test_assert_ne_fail_message() {
320        unsafe { assert_ne_unchecked!(NoCopy(0), NoCopy(0), "assertion message") }
321    }
322
323    #[test]
324    #[cfg(debug_assertions)]
325    #[should_panic(expected = "assertion message")]
326    fn test_assert_ne_fail_message_format() {
327        unsafe { assert_ne_unchecked!(NoCopy(0), NoCopy(0), "assertion {}", "message") }
328    }
329
330    #[test]
331    fn test_unreachable_unreachable() {
332        match Some(0) {
333            Some(_) => {}
334            // SAFETY: this is clearly unreachable
335            None => unsafe { unreachable_unchecked!() },
336        }
337    }
338
339    #[test]
340    #[cfg(debug_assertions)]
341    #[should_panic(expected = "internal error: entered unreachable code")]
342    fn test_unreachable_no_message() {
343        unsafe { unreachable_unchecked!() }
344    }
345
346    #[test]
347    #[cfg(debug_assertions)]
348    #[should_panic(expected = "assertion message")]
349    fn test_unreachable_message() {
350        unsafe { unreachable_unchecked!("assertion message") }
351    }
352
353    #[test]
354    #[cfg(debug_assertions)]
355    #[should_panic(expected = "assertion message")]
356    fn test_unreachable_message_format() {
357        unsafe { unreachable_unchecked!("assertion {}", "message") }
358    }
359}