1#![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
34pub trait IntCast: Sealed {
36 #[inline(always)]
41 fn cast<T: CastFromInt<Self>>(self) -> T {
42 T::cast_from(self)
43 }
44
45 #[inline(always)]
48 fn saturating_cast<T: BoundedCastFromInt<Self>>(self) -> T {
49 T::saturating_cast_from(self)
50 }
51
52 #[inline(always)]
55 fn wrapping_cast<T: BoundedCastFromInt<Self>>(self) -> T {
56 T::wrapping_cast_from(self)
57 }
58
59 #[inline(always)]
62 fn checked_cast<T: CheckedCastFromInt<Self>>(self) -> Option<T> {
63 T::checked_cast_from(self)
64 }
65
66 #[inline(always)]
68 fn strict_cast<T: CheckedCastFromInt<Self>>(self) -> T {
69 T::strict_cast_from(self)
70 }
71
72 #[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 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
108pub trait CastFromInt<T>: SealedCast<T> {
110 fn cast_from(value: T) -> Self;
112}
113
114pub trait BoundedCastFromInt<T>: SealedCast<T> {
116 fn wrapping_cast_from(value: T) -> Self;
118
119 fn saturating_cast_from(value: T) -> Self;
121}
122
123pub trait CheckedCastFromInt<T>: SealedCast<T> {
125 fn checked_cast_from(value: T) -> Option<Self>;
127
128 #[inline(always)]
135 unsafe fn unchecked_cast_from(value: T) -> Self {
136 unsafe { Self::checked_cast_from(value).unwrap_unchecked() }
137 }
138
139 #[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#[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 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 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}