bounded_integer/
prim_int.rs

1use core::fmt::{self, Display, Formatter};
2use core::num::NonZero;
3
4/// An error returned when a checked conversion into a bounded integer fails.
5#[derive(Debug, Clone, Copy)]
6#[non_exhaustive]
7pub struct TryFromError;
8
9impl Display for TryFromError {
10    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
11        f.write_str("out of range conversion to bounded integer attempted")
12    }
13}
14
15#[cfg(feature = "std")]
16impl std::error::Error for TryFromError {}
17
18#[must_use]
19pub fn try_from_error() -> TryFromError {
20    TryFromError
21}
22
23pub trait PrimInt {
24    type Signed;
25    type Unsigned;
26}
27
28pub type Signed<T> = <T as PrimInt>::Signed;
29pub type Unsigned<T> = <T as PrimInt>::Unsigned;
30
31generate! {
32    u8 i8,
33    u16 i16,
34    u32 i32,
35    u64 i64,
36    u128 i128,
37    usize isize,
38}
39
40macro_rules! generate {
41    ($($unsigned:ident $signed:ident,)*) => { $(
42        impl PrimInt for $unsigned {
43            type Signed = $signed;
44            type Unsigned = $unsigned;
45        }
46        impl PrimInt for $signed {
47            type Signed = $signed;
48            type Unsigned = $unsigned;
49        }
50
51        impl crate::__private::Dispatch<$unsigned> {
52            pub const fn checked_add_unsigned(lhs: $unsigned, rhs: $unsigned) -> Option<$unsigned> {
53                lhs.checked_add(rhs)
54            }
55            pub const fn checked_sub_unsigned(lhs: $unsigned, rhs: $unsigned) -> Option<$unsigned> {
56                lhs.checked_sub(rhs)
57            }
58            pub const fn rem_euclid_unsigned(lhs: $unsigned, rhs: NonZero<$unsigned>) -> $unsigned {
59                lhs.rem_euclid(rhs.get())
60            }
61        }
62        impl crate::__private::Dispatch<$signed> {
63            pub const fn checked_add_unsigned(lhs: $signed, rhs: $unsigned) -> Option<$signed> {
64                lhs.checked_add_unsigned(rhs)
65            }
66            pub const fn checked_sub_unsigned(lhs: $signed, rhs: $unsigned) -> Option<$signed> {
67                lhs.checked_sub_unsigned(rhs)
68            }
69            pub const fn rem_euclid_unsigned(lhs: $signed, rhs: NonZero<$unsigned>) -> $unsigned {
70                // In my benchmarks, this is faster than methods involving widening.
71                #[expect(clippy::cast_possible_wrap)]
72                #[expect(clippy::cast_sign_loss)]
73                if 0 <= lhs {
74                    // If `lhs` is nonnegative, just use regular unsigned remainder.
75                    (lhs as $unsigned).rem_euclid(rhs.get())
76                } else if 0 <= rhs.get() as $signed {
77                    // If `rhs` is small enough to fit in a signed type, use regular `rem_euclid`.
78                    // We know the result is nonnegative, so we can cast to the unsigned type.
79                    lhs.rem_euclid(rhs.get() as $signed) as $unsigned
80                } else {
81                    // Otherwise, `lhs` is negative and `rhs` is larger than all signed values. We
82                    // can therefore add `rhs` to `lhs` and get an unsigned value without overflow,
83                    // which won’t affect the result.
84                    rhs.get().checked_add_signed(lhs).unwrap().rem_euclid(rhs.get())
85                }
86            }
87        }
88    )* };
89}
90use generate;
91
92// We don’t implement for `usize` because we don’t get `(wide usize): From<usize>` impls which
93// makes things difficult trait-wise.
94pub trait HasWide {
95    type Wide;
96}
97
98pub type Wide<T> = <T as HasWide>::Wide;
99
100impl HasWide for u8 {
101    type Wide = u16;
102}
103impl HasWide for u16 {
104    type Wide = u32;
105}
106impl HasWide for u32 {
107    type Wide = u64;
108}
109impl HasWide for u64 {
110    type Wide = u128;
111}
112impl HasWide for i8 {
113    type Wide = i16;
114}
115impl HasWide for i16 {
116    type Wide = i32;
117}
118impl HasWide for i32 {
119    type Wide = i64;
120}
121impl HasWide for i64 {
122    type Wide = i128;
123}