Skip to main content

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