Skip to main content

int_cast/
lib.rs

1//! This crate provides convenient casts between primitive integers.
2//!
3//! The primary entry point is the extension trait [`IntCast`], which will
4//! add casting methods to the primitive integer types. The casts provided are:
5//!  - [`x.cast()`](IntCast::cast), only implemented when the cast is infallible,
6//!  - [`x.checked_cast()`](IntCast::checked_cast), returning `None` if the cast fails,
7//!  - [`x.strict_cast()`](IntCast::strict_cast), panicking if the cast fails,
8//!  - [`x.unchecked_cast()`](IntCast::unchecked_cast), undefined behavior if the cast fails,
9//!  - [`x.wrapping_cast()`](IntCast::wrapping_cast), never fails, wraps around, and
10//!  - [`x.saturating_cast()`](IntCast::saturating_cast), never fails, saturates at the boundary.
11//!
12//! Unlike [`x.into()`](Into::into) and [`x.try_into()`](TryInto::try_into) these casts can only
13//! convert between primitive integer types, and they support directly writing the target type using
14//! a turbofish, e.g. [`x.strict_cast::<usize>()`](IntCast::strict_cast).
15//!
16//! All cast traits are **sealed** meaning you may not implement them for custom types. This is
17//! intended, so that the behavior is predictable.
18//!
19//! # Features
20//!
21//! If you enable the `nonzero` feature all traits are extended with implementations for
22//! [`core::num::NonZero`] types. Note that this requires a nightly compiler and uses the
23//! very much unstable [`core::num::ZeroablePrimitive`].
24#![cfg_attr(not(feature = "std"), no_std)]
25#![cfg_attr(feature = "nonzero", allow(internal_features))]
26#![cfg_attr(feature = "nonzero", feature(nonzero_internals))]
27#![cfg_attr(feature = "nonzero", allow(clippy::incompatible_msrv))]
28#![deny(unsafe_op_in_unsafe_fn)]
29#![warn(missing_docs)]
30
31#[cfg(feature = "nonzero")]
32use core::num::{NonZero, ZeroablePrimitive};
33
34/// Extension trait for casting integers.
35pub trait IntCast: Sealed {
36    /// Converts `self` to the target integer type, without fail.
37    ///
38    /// Note that this is not implemented for all type combinations, only if an
39    /// infallible conversion exists.
40    #[inline(always)]
41    fn cast<T: CastFromInt<Self>>(self) -> T {
42        T::cast_from(self)
43    }
44
45    /// Converts `self` to the target integer type, saturating at the numeric
46    /// bounds instead of overflowing.
47    #[inline(always)]
48    fn saturating_cast<T: BoundedCastFromInt<Self>>(self) -> T {
49        T::saturating_cast_from(self)
50    }
51
52    /// Converts `self` to the target integer type, wrapping around at the
53    /// boundary of the target type.
54    #[inline(always)]
55    fn wrapping_cast<T: BoundedCastFromInt<Self>>(self) -> T {
56        T::wrapping_cast_from(self)
57    }
58
59    /// Converts `self` to the target integer type, returning `None` if the value
60    /// does not lie in the target type's domain.
61    #[inline(always)]
62    fn checked_cast<T: CheckedCastFromInt<Self>>(self) -> Option<T> {
63        T::checked_cast_from(self)
64    }
65
66    /// Equivalent to `self.checked_cast::<T>().unwrap()`.
67    #[inline(always)]
68    fn strict_cast<T: CheckedCastFromInt<Self>>(self) -> T {
69        T::strict_cast_from(self)
70    }
71
72    /// Equivalent to `self.checked_cast::<T>().unwrap_unchecked()`.
73    ///
74    /// # Safety
75    ///
76    /// This results in undefined behavior when `self` will overflow when
77    /// converted to the target type.
78    #[inline(always)]
79    unsafe fn unchecked_cast<T: CheckedCastFromInt<Self>>(self) -> T {
80        unsafe { T::unchecked_cast_from(self) }
81    }
82}
83
84mod private {
85    pub trait Sealed: Sized {}
86    #[cfg(feature = "nonzero")]
87    impl<T: Sealed + core::num::ZeroablePrimitive> Sealed for core::num::NonZero<T> {}
88
89    // Cast<T> : SealedCast<T> avoids the orphan rule, which would otherwise
90    // allow e.g. implementing Cast<Foo> for u8.
91    pub trait SealedCast<T>: Sized + Sealed {}
92    impl<T: Sealed, U: Sealed> SealedCast<T> for U {}
93}
94
95use private::{Sealed, SealedCast};
96
97macro_rules! mark_type {
98    ([$($T:ty),*]) => {$(
99        impl Sealed for $T { }
100        impl IntCast for $T { }
101        #[cfg(feature = "nonzero")]
102        impl IntCast for NonZero<$T> { }
103    )*};
104}
105
106mark_type!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
107
108/// Infallible conversion between integers.
109pub trait CastFromInt<T>: SealedCast<T> {
110    /// Converts `value` to this type.
111    fn cast_from(value: T) -> Self;
112}
113
114/// Conversion between integers, wrapping around or saturating at the target type's boundaries.
115pub trait BoundedCastFromInt<T>: SealedCast<T> {
116    /// Converts `value` to this type, wrapping around at the boundary of the type.
117    fn wrapping_cast_from(value: T) -> Self;
118
119    /// Converts `value` to this type, saturating at the numeric bounds instead of overflowing.
120    fn saturating_cast_from(value: T) -> Self;
121}
122
123/// Fallible conversion between integers.
124pub trait CheckedCastFromInt<T>: SealedCast<T> {
125    /// Converts `value` to this type, returning `None` if overflow would have occurred.
126    fn checked_cast_from(value: T) -> Option<Self>;
127
128    /// Converts `value` to this type, assuming overflow cannot occur.
129    ///
130    /// # Safety
131    ///
132    /// This results in undefined behavior when `value` will overflow when
133    /// converted to this type.
134    #[inline(always)]
135    unsafe fn unchecked_cast_from(value: T) -> Self {
136        unsafe { Self::checked_cast_from(value).unwrap_unchecked() }
137    }
138
139    /// Converts `value` to this type, panicking on overflow.
140    ///
141    /// # Panics
142    ///
143    /// This function will always panic on overflow, regardless of whether overflow checks are enabled.
144    #[inline(always)]
145    fn strict_cast_from(value: T) -> Self {
146        Self::checked_cast_from(value).unwrap()
147    }
148}
149
150macro_rules! impl_infallible_cast {
151    ($Src:ty as [$($Dst:ty),*]) => {$(
152        impl CastFromInt<$Src> for $Dst {
153            #[inline(always)]
154            fn cast_from(value: $Src) -> Self {
155                value.into()
156            }
157        }
158
159        #[cfg(feature = "nonzero")]
160        impl CastFromInt<NonZero<$Src>> for NonZero<$Dst> {
161            #[inline(always)]
162            fn cast_from(value: NonZero<$Src>) -> Self {
163                value.into()
164            }
165        }
166    )*};
167}
168
169macro_rules! impl_fallible_cast {
170    ($Src:ty as [$($Dst:ty),*]) => {$(
171        impl CheckedCastFromInt<$Src> for $Dst {
172            #[inline]
173            fn checked_cast_from(value: $Src) -> Option<Self> {
174                value.try_into().ok()
175            }
176        }
177
178        #[cfg(feature = "nonzero")]
179        impl CheckedCastFromInt<$Src> for NonZero<$Dst> {
180            #[inline]
181            fn checked_cast_from(value: $Src) -> Option<Self> {
182                let maybe_zero: $Dst = value.try_into().ok()?;
183                NonZero::<$Dst>::new(maybe_zero)
184            }
185        }
186
187        impl BoundedCastFromInt<$Src> for $Dst {
188            #[inline(always)]
189            fn wrapping_cast_from(value: $Src) -> Self {
190                value as Self
191            }
192
193            #[inline]
194            #[allow(unused_comparisons)]
195            #[allow(irrefutable_let_patterns)]
196            fn saturating_cast_from(value: $Src) -> Self {
197                if let Ok(x) = value.try_into() {
198                    return x;
199                }
200
201                if value < 0 { <$Dst>::MIN } else { <$Dst>::MAX }
202            }
203        }
204    )*};
205}
206
207macro_rules! impl_all_fallible_casts {
208    ([$($Src:ty),*]) => {$(
209        impl_fallible_cast!($Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
210    )*};
211}
212
213impl_infallible_cast!( u8 as [u8, u16, u32, u64, u128, usize,     i16, i32, i64, i128, isize]);
214impl_infallible_cast!(u16 as [    u16, u32, u64, u128, usize,          i32, i64, i128       ]);
215impl_infallible_cast!(u32 as [         u32, u64, u128,                      i64, i128       ]);
216impl_infallible_cast!(u64 as [              u64, u128,                           i128       ]);
217impl_infallible_cast!( i8 as [                                i8, i16, i32, i64, i128, isize]);
218impl_infallible_cast!(i16 as [                                    i16, i32, i64, i128, isize]);
219impl_infallible_cast!(i32 as [                                         i32, i64, i128       ]);
220impl_infallible_cast!(i64 as [                                              i64, i128       ]);
221impl_infallible_cast!(u128 as [u128]);
222impl_infallible_cast!(i128 as [i128]);
223impl_infallible_cast!(usize as [usize]);
224impl_infallible_cast!(isize as [isize]);
225
226impl_all_fallible_casts!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
227
228// Blanket implementations for NonZero source types. If the destination is a NonZero type it has a
229// concrete implementation above.
230#[cfg(feature = "nonzero")]
231impl<Src: Sealed + ZeroablePrimitive, Dst: CastFromInt<Src>> CastFromInt<NonZero<Src>> for Dst {
232    #[inline(always)]
233    fn cast_from(value: NonZero<Src>) -> Self {
234        Self::cast_from(value.get())
235    }
236}
237
238#[cfg(feature = "nonzero")]
239impl<Src: Sealed + ZeroablePrimitive, Dst: CheckedCastFromInt<Src>> CheckedCastFromInt<NonZero<Src>>
240    for Dst
241{
242    #[inline(always)]
243    fn checked_cast_from(value: NonZero<Src>) -> Option<Self> {
244        Self::checked_cast_from(value.get())
245    }
246}
247
248#[cfg(feature = "nonzero")]
249impl<Src: Sealed + ZeroablePrimitive, Dst: BoundedCastFromInt<Src>> BoundedCastFromInt<NonZero<Src>>
250    for Dst
251{
252    #[inline(always)]
253    fn wrapping_cast_from(value: NonZero<Src>) -> Self {
254        Self::wrapping_cast_from(value.get())
255    }
256
257    #[inline(always)]
258    fn saturating_cast_from(value: NonZero<Src>) -> Self {
259        Self::saturating_cast_from(value.get())
260    }
261}
262
263#[cfg(test)]
264mod test {
265    #[cfg(feature = "nonzero")]
266    use std::num::*;
267    use std::sync::LazyLock;
268
269    use num_bigint::BigInt;
270
271    use super::*;
272
273    #[cfg(feature = "nonzero")]
274    #[test]
275    fn test_infallible_nonzero() {
276        // Just a quick sanity check the (blanket) implementation works.
277        assert_eq!(NonZeroU8::new(4).unwrap().cast::<i16>(), 42_i16);
278        assert_eq!(
279            NonZeroU8::new(4).unwrap().cast::<NonZeroI16>().get(),
280            42_i16
281        );
282    }
283
284    // All (negative) integers which are at or near a power of two to test
285    // boundary conditions.
286    static ORDERED_VALS: LazyLock<Vec<String>> = LazyLock::new(|| {
287        let mut vals = Vec::new();
288        for exp in 0..=128 {
289            let val = BigInt::from(2).pow(exp);
290            vals.push(&val - 2);
291            vals.push(&val - 1);
292            vals.push(val.clone());
293            vals.push(&val + 1);
294            vals.push(&val + 2);
295        }
296        for val in vals.clone() {
297            vals.push(-val);
298        }
299        vals.sort();
300        vals.dedup();
301        vals.into_iter().map(|x| x.to_string()).collect()
302    });
303
304    macro_rules! make_checked_cast_test {
305        ($Src:ty as [$($Dst:ty),*]) => {$(
306            concat_idents::concat_idents!(fn_name = test_checked_cast_, $Src, _to_, $Dst {
307                #[test]
308                #[allow(non_snake_case)]
309                fn fn_name() {
310                    for val in ORDERED_VALS.iter() {
311                        if let Some(src) = val.parse::<$Src>().ok() {
312                            let dst: Option<$Dst> = val.parse().ok();
313                            assert_eq!(src.checked_cast::<$Dst>(), dst);
314                        }
315                    }
316                }
317            });
318        )*}
319    }
320
321    macro_rules! make_bounded_cast_test {
322        (|$src:ident| $raw:expr, $Src:ty as [$($Dst:ty),*]) => {$(
323            concat_idents::concat_idents!(fn_name = test_bounded_wrapping_cast_, $Src, _to_, $Dst {
324                #[test]
325                #[allow(non_snake_case)]
326                fn fn_name() {
327                    let ord_idx = |s| ORDERED_VALS.iter().position(|v| *v == s).unwrap();
328                    let dst_min_idx = ord_idx(<$Dst>::MIN.to_string());
329                    let dst_max_idx = ord_idx(<$Dst>::MAX.to_string());
330                    for (val_idx, val) in ORDERED_VALS.iter().enumerate() {
331                        if let Some($src) = val.parse::<$Src>().ok() {
332                            let dst: Option<$Dst> = val.parse().ok();
333
334                            assert_eq!($src.wrapping_cast::<$Dst>(), $raw as $Dst);
335
336                            if val_idx > dst_max_idx {
337                                assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MAX);
338                            } else if val_idx < dst_min_idx {
339                                assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MIN);
340                            } else {
341                                assert_eq!($src.saturating_cast::<$Dst>(), dst.unwrap());
342                            }
343                        }
344                    }
345                }
346            });
347        )*}
348    }
349
350    macro_rules! make_tests_for_src {
351        (|$src:ident| $raw:expr, [$($Src:ty),*]) => {$(
352            make_checked_cast_test!(             $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
353            make_bounded_cast_test!(|$src| $raw, $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
354
355            #[cfg(feature = "nonzero")]
356            make_checked_cast_test!($Src as [
357                NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
358                NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize
359            ]);
360        )*}
361    }
362
363    make_tests_for_src!(
364        |x| x,
365        [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]
366    );
367
368    #[cfg(feature = "nonzero")]
369    #[rustfmt::skip]
370    make_tests_for_src!(
371        |x| x.get(),
372        [
373            NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
374            NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize
375        ]
376    );
377}