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