num_conv/
lib.rs

1//! `num_conv` is a crate to convert between integer types without using `as` casts. This provides
2//! better certainty when refactoring, makes the exact behavior of code more explicit, and allows
3//! using turbofish syntax.
4
5#![no_std]
6
7/// Anonymously import all extension traits.
8///
9/// This allows you to use the methods without worrying about polluting the namespace or importing
10/// them individually.
11///
12/// ```rust
13/// use num_conv::prelude::*;
14/// ```
15pub mod prelude {
16    pub use crate::{CastSigned as _, CastUnsigned as _, Extend as _, Truncate as _};
17}
18
19mod sealed {
20    pub trait Integer {}
21
22    macro_rules! impl_integer {
23        ($($t:ty)*) => {$(
24            impl Integer for $t {}
25        )*};
26    }
27
28    impl_integer! {
29        u8 u16 u32 u64 u128 usize
30        i8 i16 i32 i64 i128 isize
31    }
32
33    pub trait ExtendTargetSealed<T> {
34        fn extend(self) -> T;
35    }
36
37    pub trait TruncateTargetSealed<T> {
38        fn truncate(self) -> T;
39    }
40}
41
42/// Cast to a signed integer of the same size.
43///
44/// This trait is implemented for all integers. Unsigned to signed casts are equivalent to
45/// `0.wrapping_add_signed(value)`, while signed to signed casts are an identity conversion.
46///
47/// ```rust
48/// # use num_conv::CastSigned;
49/// assert_eq!(u8::MAX.cast_signed(), -1_i8);
50/// assert_eq!(u16::MAX.cast_signed(), -1_i16);
51/// assert_eq!(u32::MAX.cast_signed(), -1_i32);
52/// assert_eq!(u64::MAX.cast_signed(), -1_i64);
53/// assert_eq!(u128::MAX.cast_signed(), -1_i128);
54/// assert_eq!(usize::MAX.cast_signed(), -1_isize);
55/// ```
56///
57/// ```rust
58/// # use num_conv::CastSigned;
59/// assert_eq!(0_i8.cast_signed(), 0_i8);
60/// assert_eq!(0_i16.cast_signed(), 0_i16);
61/// assert_eq!(0_i32.cast_signed(), 0_i32);
62/// assert_eq!(0_i64.cast_signed(), 0_i64);
63/// assert_eq!(0_i128.cast_signed(), 0_i128);
64/// assert_eq!(0_isize.cast_signed(), 0_isize);
65/// ```
66pub trait CastSigned: sealed::Integer {
67    /// The signed integer type with the same size as `Self`.
68    type Signed;
69
70    /// Cast an integer to the signed integer of the same size.
71    fn cast_signed(self) -> Self::Signed;
72}
73
74/// Cast to an unsigned integer of the same size.
75///
76/// This trait is implemented for all integers. Signed to unsigned casts are equivalent to
77/// `0.wrapping_add_unsigned(value)`, while unsigned to unsigned casts are an identity conversion.
78///
79/// ```rust
80/// # use num_conv::CastUnsigned;
81/// assert_eq!((-1_i8).cast_unsigned(), u8::MAX);
82/// assert_eq!((-1_i16).cast_unsigned(), u16::MAX);
83/// assert_eq!((-1_i32).cast_unsigned(), u32::MAX);
84/// assert_eq!((-1_i64).cast_unsigned(), u64::MAX);
85/// assert_eq!((-1_i128).cast_unsigned(), u128::MAX);
86/// assert_eq!((-1_isize).cast_unsigned(), usize::MAX);
87/// ```
88///
89/// ```rust
90/// # use num_conv::CastUnsigned;
91/// assert_eq!(0_u8.cast_unsigned(), 0_u8);
92/// assert_eq!(0_u16.cast_unsigned(), 0_u16);
93/// assert_eq!(0_u32.cast_unsigned(), 0_u32);
94/// assert_eq!(0_u64.cast_unsigned(), 0_u64);
95/// assert_eq!(0_u128.cast_unsigned(), 0_u128);
96/// assert_eq!(0_usize.cast_unsigned(), 0_usize);
97/// ```
98pub trait CastUnsigned: sealed::Integer {
99    /// The unsigned integer type with the same size as `Self`.
100    type Unsigned;
101
102    /// Cast an integer to the unsigned integer of the same size.
103    fn cast_unsigned(self) -> Self::Unsigned;
104}
105
106/// A type that can be used with turbofish syntax in [`Extend::extend`].
107///
108/// It is unlikely that you will want to use this trait directly. You are probably looking for the
109/// [`Extend`] trait.
110pub trait ExtendTarget<T>: sealed::ExtendTargetSealed<T> {}
111
112/// A type that can be used with turbofish syntax in [`Truncate::truncate`].
113///
114/// It is unlikely that you will want to use this trait directly. You are probably looking for the
115/// [`Truncate`] trait.
116pub trait TruncateTarget<T>: sealed::TruncateTargetSealed<T> {}
117
118/// Extend to an integer of the same size or larger, preserving its value.
119///
120/// ```rust
121/// # use num_conv::Extend;
122/// assert_eq!(0_u8.extend::<u16>(), 0_u16);
123/// assert_eq!(0_u16.extend::<u32>(), 0_u32);
124/// assert_eq!(0_u32.extend::<u64>(), 0_u64);
125/// assert_eq!(0_u64.extend::<u128>(), 0_u128);
126/// ```
127///
128/// ```rust
129/// # use num_conv::Extend;
130/// assert_eq!((-1_i8).extend::<i16>(), -1_i16);
131/// assert_eq!((-1_i16).extend::<i32>(), -1_i32);
132/// assert_eq!((-1_i32).extend::<i64>(), -1_i64);
133/// assert_eq!((-1_i64).extend::<i128>(), -1_i128);
134/// ```
135pub trait Extend: sealed::Integer {
136    /// Extend an integer to an integer of the same size or larger, preserving its value.
137    fn extend<T>(self) -> T
138    where
139        Self: ExtendTarget<T>;
140}
141
142impl<T: sealed::Integer> Extend for T {
143    fn extend<U>(self) -> U
144    where
145        T: ExtendTarget<U>,
146    {
147        sealed::ExtendTargetSealed::extend(self)
148    }
149}
150
151/// Truncate to an integer of the same size or smaller, preserving the least significant bits.
152///
153/// ```rust
154/// # use num_conv::Truncate;
155/// assert_eq!(u16::MAX.truncate::<u8>(), u8::MAX);
156/// assert_eq!(u32::MAX.truncate::<u16>(), u16::MAX);
157/// assert_eq!(u64::MAX.truncate::<u32>(), u32::MAX);
158/// assert_eq!(u128::MAX.truncate::<u64>(), u64::MAX);
159/// ```
160///
161/// ```rust
162/// # use num_conv::Truncate;
163/// assert_eq!((-1_i16).truncate::<i8>(), -1_i8);
164/// assert_eq!((-1_i32).truncate::<i16>(), -1_i16);
165/// assert_eq!((-1_i64).truncate::<i32>(), -1_i32);
166/// assert_eq!((-1_i128).truncate::<i64>(), -1_i64);
167/// ```
168pub trait Truncate: sealed::Integer {
169    /// Truncate an integer to an integer of the same size or smaller, preserving the least
170    /// significant bits.
171    fn truncate<T>(self) -> T
172    where
173        Self: TruncateTarget<T>;
174}
175
176impl<T: sealed::Integer> Truncate for T {
177    fn truncate<U>(self) -> U
178    where
179        T: TruncateTarget<U>,
180    {
181        sealed::TruncateTargetSealed::truncate(self)
182    }
183}
184
185macro_rules! impl_cast_signed {
186    ($($($from:ty),+ => $to:ty;)*) => {$($(
187        const _: () = assert!(
188            core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
189            concat!(
190                "cannot cast ",
191                stringify!($from),
192                " to ",
193                stringify!($to),
194                " because they are different sizes"
195            )
196        );
197
198        impl CastSigned for $from {
199            type Signed = $to;
200            fn cast_signed(self) -> Self::Signed {
201                self as _
202            }
203        }
204    )+)*};
205}
206
207macro_rules! impl_cast_unsigned {
208    ($($($from:ty),+ => $to:ty;)*) => {$($(
209        const _: () = assert!(
210            core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
211            concat!(
212                "cannot cast ",
213                stringify!($from),
214                " to ",
215                stringify!($to),
216                " because they are different sizes"
217            )
218        );
219
220        impl CastUnsigned for $from {
221            type Unsigned = $to;
222            fn cast_unsigned(self) -> Self::Unsigned {
223                self as _
224            }
225        }
226    )+)*};
227}
228
229macro_rules! impl_extend {
230    ($($from:ty => $($to:ty),+;)*) => {$($(
231        const _: () = assert!(
232            core::mem::size_of::<$from>() <= core::mem::size_of::<$to>(),
233            concat!(
234                "cannot extend ",
235                stringify!($from),
236                " to ",
237                stringify!($to),
238                " because ",
239                stringify!($from),
240                " is larger than ",
241                stringify!($to)
242            )
243        );
244
245        impl sealed::ExtendTargetSealed<$to> for $from {
246            fn extend(self) -> $to {
247                self as _
248            }
249        }
250
251        impl ExtendTarget<$to> for $from {}
252    )+)*};
253}
254
255macro_rules! impl_truncate {
256    ($($($from:ty),+ => $to:ty;)*) => {$($(
257        const _: () = assert!(
258            core::mem::size_of::<$from>() >= core::mem::size_of::<$to>(),
259            concat!(
260                "cannot truncate ",
261                stringify!($from),
262                " to ",
263                stringify!($to),
264                " because ",
265                stringify!($from),
266                " is smaller than ",
267                stringify!($to)
268            )
269        );
270
271        impl sealed::TruncateTargetSealed<$to> for $from {
272            fn truncate(self) -> $to {
273                self as _
274            }
275        }
276
277        impl TruncateTarget<$to> for $from {}
278    )+)*};
279}
280
281impl_cast_signed! {
282    u8, i8 => i8;
283    u16, i16 => i16;
284    u32, i32 => i32;
285    u64, i64 => i64;
286    u128, i128 => i128;
287    usize, isize => isize;
288}
289
290impl_cast_unsigned! {
291    u8, i8 => u8;
292    u16, i16 => u16;
293    u32, i32 => u32;
294    u64, i64 => u64;
295    u128, i128 => u128;
296    usize, isize => usize;
297}
298
299impl_extend! {
300    u8 => u8, u16, u32, u64, u128, usize;
301    u16 => u16, u32, u64, u128, usize;
302    u32 => u32, u64, u128;
303    u64 => u64, u128;
304    u128 => u128;
305    usize => usize;
306
307    i8 => i8, i16, i32, i64, i128, isize;
308    i16 => i16, i32, i64, i128, isize;
309    i32 => i32, i64, i128;
310    i64 => i64, i128;
311    i128 => i128;
312    isize => isize;
313}
314
315impl_truncate! {
316    u8, u16, u32, u64, u128, usize => u8;
317    u16, u32, u64, u128, usize => u16;
318    u32, u64, u128 => u32;
319    u64, u128 => u64;
320    u128 => u128;
321    usize => usize;
322
323    i8, i16, i32, i64, i128, isize => i8;
324    i16, i32, i64, i128, isize => i16;
325    i32, i64, i128 => i32;
326    i64, i128 => i64;
327    i128 => i128;
328    isize => isize;
329}