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}