#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "nonzero", allow(internal_features))]
#![cfg_attr(feature = "nonzero", feature(nonzero_internals))]
#![cfg_attr(feature = "nonzero", allow(clippy::incompatible_msrv))]
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(missing_docs)]
#[cfg(feature = "nonzero")]
use core::num::{NonZero, ZeroablePrimitive};
pub trait IntCast: Sealed {
#[inline(always)]
fn cast<T: CastFromInt<Self>>(self) -> T {
T::cast_from(self)
}
#[inline(always)]
fn saturating_cast<T: BoundedCastFromInt<Self>>(self) -> T {
T::saturating_cast_from(self)
}
#[inline(always)]
fn wrapping_cast<T: BoundedCastFromInt<Self>>(self) -> T {
T::wrapping_cast_from(self)
}
#[inline(always)]
fn checked_cast<T: CheckedCastFromInt<Self>>(self) -> Option<T> {
T::checked_cast_from(self)
}
#[inline(always)]
fn strict_cast<T: CheckedCastFromInt<Self>>(self) -> T {
T::strict_cast_from(self)
}
#[inline(always)]
unsafe fn unchecked_cast<T: CheckedCastFromInt<Self>>(self) -> T {
unsafe { T::unchecked_cast_from(self) }
}
}
mod private {
pub trait Sealed: Sized {}
#[cfg(feature = "nonzero")]
impl<T: Sealed + core::num::ZeroablePrimitive> Sealed for core::num::NonZero<T> {}
pub trait SealedCast<T>: Sized + Sealed {}
impl<T: Sealed, U: Sealed> SealedCast<T> for U {}
}
use private::{Sealed, SealedCast};
macro_rules! mark_type {
([$($T:ty),*]) => {$(
impl Sealed for $T { }
impl IntCast for $T { }
#[cfg(feature = "nonzero")]
impl IntCast for NonZero<$T> { }
)*};
}
mark_type!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
pub trait CastFromInt<T>: SealedCast<T> {
fn cast_from(value: T) -> Self;
}
pub trait BoundedCastFromInt<T>: SealedCast<T> {
fn wrapping_cast_from(value: T) -> Self;
fn saturating_cast_from(value: T) -> Self;
}
pub trait CheckedCastFromInt<T>: SealedCast<T> {
fn checked_cast_from(value: T) -> Option<Self>;
#[inline(always)]
unsafe fn unchecked_cast_from(value: T) -> Self {
unsafe { Self::checked_cast_from(value).unwrap_unchecked() }
}
#[inline(always)]
fn strict_cast_from(value: T) -> Self {
Self::checked_cast_from(value).unwrap()
}
}
macro_rules! impl_infallible_cast {
($Src:ty as [$($Dst:ty),*]) => {$(
impl CastFromInt<$Src> for $Dst {
#[inline(always)]
fn cast_from(value: $Src) -> Self {
value.into()
}
}
#[cfg(feature = "nonzero")]
impl CastFromInt<NonZero<$Src>> for NonZero<$Dst> {
#[inline(always)]
fn cast_from(value: NonZero<$Src>) -> Self {
value.into()
}
}
)*};
}
macro_rules! impl_fallible_cast {
($Src:ty as [$($Dst:ty),*]) => {$(
impl CheckedCastFromInt<$Src> for $Dst {
#[inline]
fn checked_cast_from(value: $Src) -> Option<Self> {
value.try_into().ok()
}
}
#[cfg(feature = "nonzero")]
impl CheckedCastFromInt<$Src> for NonZero<$Dst> {
#[inline]
fn checked_cast_from(value: $Src) -> Option<Self> {
let maybe_zero: $Dst = value.try_into().ok()?;
NonZero::<$Dst>::new(maybe_zero)
}
}
impl BoundedCastFromInt<$Src> for $Dst {
#[inline(always)]
fn wrapping_cast_from(value: $Src) -> Self {
value as Self
}
#[inline]
#[allow(unused_comparisons)]
#[allow(irrefutable_let_patterns)]
fn saturating_cast_from(value: $Src) -> Self {
if let Ok(x) = value.try_into() {
return x;
}
if value < 0 { <$Dst>::MIN } else { <$Dst>::MAX }
}
}
)*};
}
macro_rules! impl_all_fallible_casts {
([$($Src:ty),*]) => {$(
impl_fallible_cast!($Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
)*};
}
impl_infallible_cast!( u8 as [u8, u16, u32, u64, u128, usize, i16, i32, i64, i128, isize]);
impl_infallible_cast!(u16 as [ u16, u32, u64, u128, usize, i32, i64, i128 ]);
impl_infallible_cast!(u32 as [ u32, u64, u128, i64, i128 ]);
impl_infallible_cast!(u64 as [ u64, u128, i128 ]);
impl_infallible_cast!( i8 as [ i8, i16, i32, i64, i128, isize]);
impl_infallible_cast!(i16 as [ i16, i32, i64, i128, isize]);
impl_infallible_cast!(i32 as [ i32, i64, i128 ]);
impl_infallible_cast!(i64 as [ i64, i128 ]);
impl_infallible_cast!(u128 as [u128]);
impl_infallible_cast!(i128 as [i128]);
impl_infallible_cast!(usize as [usize]);
impl_infallible_cast!(isize as [isize]);
impl_all_fallible_casts!([u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
#[cfg(feature = "nonzero")]
impl<Src: Sealed + ZeroablePrimitive, Dst: CastFromInt<Src>> CastFromInt<NonZero<Src>> for Dst {
#[inline(always)]
fn cast_from(value: NonZero<Src>) -> Self {
Self::cast_from(value.get())
}
}
#[cfg(feature = "nonzero")]
impl<Src: Sealed + ZeroablePrimitive, Dst: CheckedCastFromInt<Src>> CheckedCastFromInt<NonZero<Src>>
for Dst
{
#[inline(always)]
fn checked_cast_from(value: NonZero<Src>) -> Option<Self> {
Self::checked_cast_from(value.get())
}
}
#[cfg(feature = "nonzero")]
impl<Src: Sealed + ZeroablePrimitive, Dst: BoundedCastFromInt<Src>> BoundedCastFromInt<NonZero<Src>>
for Dst
{
#[inline(always)]
fn wrapping_cast_from(value: NonZero<Src>) -> Self {
Self::wrapping_cast_from(value.get())
}
#[inline(always)]
fn saturating_cast_from(value: NonZero<Src>) -> Self {
Self::saturating_cast_from(value.get())
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "nonzero")]
use std::num::*;
use std::sync::LazyLock;
use num_bigint::BigInt;
use super::*;
#[cfg(feature = "nonzero")]
#[test]
fn test_infallible_nonzero() {
assert_eq!(NonZeroU8::new(4).unwrap().cast::<i16>(), 42_i16);
assert_eq!(
NonZeroU8::new(4).unwrap().cast::<NonZeroI16>().get(),
42_i16
);
}
static ORDERED_VALS: LazyLock<Vec<String>> = LazyLock::new(|| {
let mut vals = Vec::new();
for exp in 0..=128 {
let val = BigInt::from(2).pow(exp);
vals.push(&val - 2);
vals.push(&val - 1);
vals.push(val.clone());
vals.push(&val + 1);
vals.push(&val + 2);
}
for val in vals.clone() {
vals.push(-val);
}
vals.sort();
vals.dedup();
vals.into_iter().map(|x| x.to_string()).collect()
});
macro_rules! make_checked_cast_test {
($Src:ty as [$($Dst:ty),*]) => {$(
concat_idents::concat_idents!(fn_name = test_checked_cast_, $Src, _to_, $Dst {
#[test]
#[allow(non_snake_case)]
fn fn_name() {
for val in ORDERED_VALS.iter() {
if let Some(src) = val.parse::<$Src>().ok() {
let dst: Option<$Dst> = val.parse().ok();
assert_eq!(src.checked_cast::<$Dst>(), dst);
}
}
}
});
)*}
}
macro_rules! make_bounded_cast_test {
(|$src:ident| $raw:expr, $Src:ty as [$($Dst:ty),*]) => {$(
concat_idents::concat_idents!(fn_name = test_bounded_wrapping_cast_, $Src, _to_, $Dst {
#[test]
#[allow(non_snake_case)]
fn fn_name() {
let ord_idx = |s| ORDERED_VALS.iter().position(|v| *v == s).unwrap();
let dst_min_idx = ord_idx(<$Dst>::MIN.to_string());
let dst_max_idx = ord_idx(<$Dst>::MAX.to_string());
for (val_idx, val) in ORDERED_VALS.iter().enumerate() {
if let Some($src) = val.parse::<$Src>().ok() {
let dst: Option<$Dst> = val.parse().ok();
assert_eq!($src.wrapping_cast::<$Dst>(), $raw as $Dst);
if val_idx > dst_max_idx {
assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MAX);
} else if val_idx < dst_min_idx {
assert_eq!($src.saturating_cast::<$Dst>(), <$Dst>::MIN);
} else {
assert_eq!($src.saturating_cast::<$Dst>(), dst.unwrap());
}
}
}
}
});
)*}
}
macro_rules! make_tests_for_src {
(|$src:ident| $raw:expr, [$($Src:ty),*]) => {$(
make_checked_cast_test!( $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
make_bounded_cast_test!(|$src| $raw, $Src as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]);
#[cfg(feature = "nonzero")]
make_checked_cast_test!($Src as [
NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize
]);
)*}
}
make_tests_for_src!(
|x| x,
[u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize]
);
#[cfg(feature = "nonzero")]
#[rustfmt::skip]
make_tests_for_src!(
|x| x.get(),
[
NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize
]
);
}