Skip to main content

const_num_traits/ops/
strict.rs

1//! Strict arithmetic: like the unchecked operators but guaranteed to panic on
2//! overflow in every build profile, mirroring the `strict_*` inherent methods
3//! on the primitive integer types (stable in std since Rust 1.91).
4//!
5//! Bodies are hand-rolled on top of the `checked_*`/`overflowing_*` inherent
6//! methods (using the same panic messages as std) so the crate's MSRV doesn't
7//! have to rise to 1.91.
8//!
9//! **CT tier C (CT-hostile)**: panicking on overflow is data-dependent
10//! control flow by definition.
11
12use core::ops::{Add, Div, Mul, Neg, Rem, Shl, Shr, Sub};
13
14use super::euclid::Euclid;
15
16c0nst::c0nst! {
17/// Performs addition that panics on overflow, even in release builds.
18pub c0nst trait StrictAdd: Sized + [c0nst] Add<Self> {
19    /// Strict addition. Computes `self + v`, panicking on overflow regardless
20    /// of whether overflow checks are enabled.
21    ///
22    /// ```
23    /// use const_num_traits::StrictAdd;
24    ///
25    /// assert_eq!(StrictAdd::strict_add(5u8, 250), 255);
26    /// ```
27    fn strict_add(self, v: Self) -> <Self as Add<Self>>::Output;
28}
29}
30
31c0nst::c0nst! {
32/// Performs subtraction that panics on overflow, even in release builds.
33pub c0nst trait StrictSub: Sized + [c0nst] Sub<Self> {
34    /// Strict subtraction. Computes `self - v`, panicking on overflow
35    /// regardless of whether overflow checks are enabled.
36    fn strict_sub(self, v: Self) -> <Self as Sub<Self>>::Output;
37}
38}
39
40c0nst::c0nst! {
41/// Performs multiplication that panics on overflow, even in release builds.
42pub c0nst trait StrictMul: Sized + [c0nst] Mul<Self> {
43    /// Strict multiplication. Computes `self * v`, panicking on overflow
44    /// regardless of whether overflow checks are enabled.
45    fn strict_mul(self, v: Self) -> <Self as Mul<Self>>::Output;
46}
47}
48
49macro_rules! strict_binary_impl {
50    ($trait_name:ident, $method:ident, $overflowing:ident, $msg:expr, $t:ty) => {
51        c0nst::c0nst! {
52        c0nst impl $trait_name for $t {
53            #[inline]
54            #[track_caller]
55            fn $method(self, v: Self) -> Self {
56                let (val, overflowed) = <$t>::$overflowing(self, v);
57                if overflowed {
58                    panic!($msg)
59                }
60                val
61            }
62        }
63        }
64    };
65}
66
67macro_rules! strict_binary_impl_all {
68    ($trait_name:ident, $method:ident, $overflowing:ident, $msg:expr) => {
69        strict_binary_impl!($trait_name, $method, $overflowing, $msg, u8);
70        strict_binary_impl!($trait_name, $method, $overflowing, $msg, u16);
71        strict_binary_impl!($trait_name, $method, $overflowing, $msg, u32);
72        strict_binary_impl!($trait_name, $method, $overflowing, $msg, u64);
73        strict_binary_impl!($trait_name, $method, $overflowing, $msg, usize);
74        strict_binary_impl!($trait_name, $method, $overflowing, $msg, u128);
75        strict_binary_impl!($trait_name, $method, $overflowing, $msg, i8);
76        strict_binary_impl!($trait_name, $method, $overflowing, $msg, i16);
77        strict_binary_impl!($trait_name, $method, $overflowing, $msg, i32);
78        strict_binary_impl!($trait_name, $method, $overflowing, $msg, i64);
79        strict_binary_impl!($trait_name, $method, $overflowing, $msg, isize);
80        strict_binary_impl!($trait_name, $method, $overflowing, $msg, i128);
81    };
82}
83
84strict_binary_impl_all!(
85    StrictAdd,
86    strict_add,
87    overflowing_add,
88    "attempt to add with overflow"
89);
90strict_binary_impl_all!(
91    StrictSub,
92    strict_sub,
93    overflowing_sub,
94    "attempt to subtract with overflow"
95);
96strict_binary_impl_all!(
97    StrictMul,
98    strict_mul,
99    overflowing_mul,
100    "attempt to multiply with overflow"
101);
102
103c0nst::c0nst! {
104/// Performs division that panics on overflow, even in release builds.
105pub c0nst trait StrictDiv: Sized + [c0nst] Div<Self> {
106    /// Strict division. Computes `self / v`, panicking on overflow (only
107    /// possible for `MIN / -1` on a signed type) or if `v` is zero.
108    fn strict_div(self, v: Self) -> <Self as Div<Self>>::Output;
109}
110}
111
112c0nst::c0nst! {
113/// Performs a remainder operation that panics on overflow, even in release builds.
114pub c0nst trait StrictRem: Sized + [c0nst] Rem<Self> {
115    /// Strict remainder. Computes `self % v`, panicking on overflow (only
116    /// possible for `MIN % -1` on a signed type) or if `v` is zero.
117    fn strict_rem(self, v: Self) -> <Self as Rem<Self>>::Output;
118}
119}
120
121strict_binary_impl_all!(
122    StrictDiv,
123    strict_div,
124    overflowing_div,
125    "attempt to divide with overflow"
126);
127strict_binary_impl_all!(
128    StrictRem,
129    strict_rem,
130    overflowing_rem,
131    "attempt to calculate the remainder with overflow"
132);
133
134c0nst::c0nst! {
135/// Performs negation that panics on overflow, even in release builds.
136pub c0nst trait StrictNeg: Sized {
137    /// Strict negation. Computes `-self`, panicking on overflow: for unsigned
138    /// types any nonzero value overflows, for signed types only `MIN` does.
139    type Output;
140    fn strict_neg(self) -> Self::Output;
141}
142}
143
144macro_rules! strict_neg_impl {
145    ($($t:ty)*) => {$(
146        c0nst::c0nst! {
147        c0nst impl StrictNeg for $t {
148            type Output = $t;
149            #[inline]
150            #[track_caller]
151            fn strict_neg(self) -> Self {
152                let (val, overflowed) = <$t>::overflowing_neg(self);
153                if overflowed {
154                    panic!("attempt to negate with overflow")
155                }
156                val
157            }
158        }
159        }
160    )*}
161}
162
163strict_neg_impl!(usize u8 u16 u32 u64 u128);
164strict_neg_impl!(isize i8 i16 i32 i64 i128);
165
166c0nst::c0nst! {
167/// Computes the absolute value, panicking on overflow even in release builds.
168pub c0nst trait StrictAbs: Sized + [c0nst] Neg {
169    /// Strict absolute value. Computes `self.abs()`, panicking for
170    /// `MIN.strict_abs()` (the result can't be represented).
171    fn strict_abs(self) -> <Self as Neg>::Output;
172}
173}
174
175macro_rules! strict_abs_impl {
176    ($($t:ty)*) => {$(
177        c0nst::c0nst! {
178        c0nst impl StrictAbs for $t {
179            #[inline]
180            #[track_caller]
181            fn strict_abs(self) -> Self {
182                // std routes strict_abs through the negation overflow panic.
183                match <$t>::checked_abs(self) {
184                    Some(val) => val,
185                    None => panic!("attempt to negate with overflow"),
186                }
187            }
188        }
189        }
190    )*}
191}
192
193strict_abs_impl!(isize i8 i16 i32 i64 i128);
194
195c0nst::c0nst! {
196/// Performs a left shift that panics on overflowing shift amounts, even in release builds.
197pub c0nst trait StrictShl: Sized + [c0nst] Shl<u32> {
198    /// Strict shift left. Computes `self << rhs`, panicking if `rhs` is
199    /// larger than or equal to the number of bits in `self`.
200    fn strict_shl(self, rhs: u32) -> <Self as Shl<u32>>::Output;
201}
202}
203
204c0nst::c0nst! {
205/// Performs a right shift that panics on overflowing shift amounts, even in release builds.
206pub c0nst trait StrictShr: Sized + [c0nst] Shr<u32> {
207    /// Strict shift right. Computes `self >> rhs`, panicking if `rhs` is
208    /// larger than or equal to the number of bits in `self`.
209    fn strict_shr(self, rhs: u32) -> <Self as Shr<u32>>::Output;
210}
211}
212
213macro_rules! strict_shift_impl {
214    ($trait_name:ident, $method:ident, $checked:ident, $msg:expr, $($t:ty)*) => {$(
215        c0nst::c0nst! {
216        c0nst impl $trait_name for $t {
217            #[inline]
218            #[track_caller]
219            fn $method(self, rhs: u32) -> Self {
220                match <$t>::$checked(self, rhs) {
221                    Some(val) => val,
222                    None => panic!($msg),
223                }
224            }
225        }
226        }
227    )*}
228}
229
230strict_shift_impl!(
231    StrictShl,
232    strict_shl,
233    checked_shl,
234    "attempt to shift left with overflow",
235    usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128
236);
237strict_shift_impl!(
238    StrictShr,
239    strict_shr,
240    checked_shr,
241    "attempt to shift right with overflow",
242    usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128
243);
244
245c0nst::c0nst! {
246/// Performs exponentiation that panics on overflow, even in release builds.
247pub c0nst trait StrictPow: Sized + [c0nst] Mul<Self> {
248    /// Strict exponentiation. Computes `self.pow(exp)`, panicking on
249    /// overflow regardless of whether overflow checks are enabled.
250    fn strict_pow(self, exp: u32) -> <Self as Mul<Self>>::Output;
251}
252}
253
254strict_shift_impl!(
255    StrictPow,
256    strict_pow,
257    checked_pow,
258    "attempt to exponentiate with overflow",
259    usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128
260);
261
262c0nst::c0nst! {
263/// Performs Euclidean division and remainder that panic on overflow, even in release builds.
264pub c0nst trait StrictEuclid: [c0nst] Euclid {
265    /// Strict Euclidean division. Computes `Euclid::div_euclid(self, v)`,
266    /// panicking on overflow (only possible for `MIN / -1` on a signed type)
267    /// or if `v` is zero.
268    fn strict_div_euclid(self, v: Self) -> <Self as Div<Self>>::Output;
269
270    /// Strict Euclidean remainder. Computes `Euclid::rem_euclid(self, v)`,
271    /// panicking on overflow (only possible for `MIN % -1` on a signed type)
272    /// or if `v` is zero.
273    fn strict_rem_euclid(self, v: Self) -> <Self as Rem<Self>>::Output;
274}
275}
276
277macro_rules! strict_euclid_impl {
278    ($($t:ty)*) => {$(
279        c0nst::c0nst! {
280        c0nst impl StrictEuclid for $t {
281            #[inline]
282            #[track_caller]
283            fn strict_div_euclid(self, v: $t) -> Self {
284                let (val, overflowed) = <$t>::overflowing_div_euclid(self, v);
285                if overflowed {
286                    panic!("attempt to divide with overflow")
287                }
288                val
289            }
290
291            #[inline]
292            #[track_caller]
293            fn strict_rem_euclid(self, v: $t) -> Self {
294                let (val, overflowed) = <$t>::overflowing_rem_euclid(self, v);
295                if overflowed {
296                    panic!("attempt to calculate the remainder with overflow")
297                }
298                val
299            }
300        }
301        }
302    )*}
303}
304
305strict_euclid_impl!(usize u8 u16 u32 u64 u128);
306strict_euclid_impl!(isize i8 i16 i32 i64 i128);
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    #[test]
313    fn strict_ok_paths() {
314        assert_eq!(StrictAdd::strict_add(250u8, 5), 255);
315        assert_eq!(StrictSub::strict_sub(5i8, 10), -5);
316        assert_eq!(StrictMul::strict_mul(16u8, 15), 240);
317        assert_eq!(StrictDiv::strict_div(100i32, -3), -33);
318        assert_eq!(StrictRem::strict_rem(100i32, -3), 1);
319        assert_eq!(StrictNeg::strict_neg(-100i8), 100);
320        assert_eq!(StrictNeg::strict_neg(0u8), 0);
321        assert_eq!(StrictAbs::strict_abs(-100i8), 100);
322        assert_eq!(StrictShl::strict_shl(1u8, 7), 0x80);
323        assert_eq!(StrictShr::strict_shr(0x80u8, 7), 1);
324        assert_eq!(StrictPow::strict_pow(3u8, 5), 243);
325        assert_eq!(StrictEuclid::strict_div_euclid(-7i32, 4), -2);
326        assert_eq!(StrictEuclid::strict_rem_euclid(-7i32, 4), 1);
327    }
328
329    #[test]
330    #[should_panic(expected = "attempt to add with overflow")]
331    fn strict_add_panics() {
332        let _ = StrictAdd::strict_add(u8::MAX, 1);
333    }
334
335    #[test]
336    #[should_panic(expected = "attempt to negate with overflow")]
337    fn strict_neg_unsigned_panics() {
338        let _ = StrictNeg::strict_neg(1u8);
339    }
340
341    #[test]
342    #[should_panic(expected = "attempt to negate with overflow")]
343    fn strict_abs_panics() {
344        let _ = StrictAbs::strict_abs(i8::MIN);
345    }
346
347    #[test]
348    #[should_panic(expected = "attempt to shift left with overflow")]
349    fn strict_shl_panics() {
350        let _ = StrictShl::strict_shl(1u8, 8);
351    }
352
353    #[test]
354    #[should_panic(expected = "attempt to exponentiate with overflow")]
355    fn strict_pow_panics() {
356        let _ = StrictPow::strict_pow(2u8, 8);
357    }
358
359    #[test]
360    #[should_panic(expected = "attempt to divide with overflow")]
361    fn strict_div_euclid_panics() {
362        let _ = StrictEuclid::strict_div_euclid(i8::MIN, -1);
363    }
364}