int-cast 0.1.0

Convenient casts between primitive integers.
Documentation
//! This crate provides convenient casts between primitive integers.
//!
//! The primary entry point is the extension trait [`IntCast`], which will
//! add casting methods to the primitive integer types. The casts provided are:
//!  - [`x.cast()`](IntCast::cast), only implemented when the cast is infallible,
//!  - [`x.checked_cast()`](IntCast::checked_cast), returning `None` if the cast fails,
//!  - [`x.strict_cast()`](IntCast::strict_cast), panicking if the cast fails,
//!  - [`x.unchecked_cast()`](IntCast::unchecked_cast), undefined behavior if the cast fails,
//!  - [`x.wrapping_cast()`](IntCast::wrapping_cast), never fails, wraps around, and
//!  - [`x.saturating_cast()`](IntCast::saturating_cast), never fails, saturates at the boundary.
//!
//! Unlike [`x.into()`](Into::into) and [`x.try_into()`](TryInto::try_into) these casts can only
//! convert between primitive integer types, and they support directly writing the target type using
//! a turbofish, e.g. [`x.strict_cast::<usize>()`](IntCast::strict_cast).
//!
//! All cast traits are **sealed** meaning you may not implement them for custom types. This is
//! intended, so that the behavior is predictable.
//!
//! # Features
//!
//! If you enable the `nonzero` feature all traits are extended with implementations for
//! [`core::num::NonZero`] types. Note that this requires a nightly compiler and uses the
//! very much unstable [`core::num::ZeroablePrimitive`].
#![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};

/// Extension trait for casting integers.
pub trait IntCast: Sealed {
    /// Converts `self` to the target integer type, without fail.
    ///
    /// Note that this is not implemented for all type combinations, only if an
    /// infallible conversion exists.
    #[inline(always)]
    fn cast<T: CastFromInt<Self>>(self) -> T {
        T::cast_from(self)
    }

    /// Converts `self` to the target integer type, saturating at the numeric
    /// bounds instead of overflowing.
    #[inline(always)]
    fn saturating_cast<T: BoundedCastFromInt<Self>>(self) -> T {
        T::saturating_cast_from(self)
    }

    /// Converts `self` to the target integer type, wrapping around at the
    /// boundary of the target type.
    #[inline(always)]
    fn wrapping_cast<T: BoundedCastFromInt<Self>>(self) -> T {
        T::wrapping_cast_from(self)
    }

    /// Converts `self` to the target integer type, returning `None` if the value
    /// does not lie in the target type's domain.
    #[inline(always)]
    fn checked_cast<T: CheckedCastFromInt<Self>>(self) -> Option<T> {
        T::checked_cast_from(self)
    }

    /// Equivalent to `self.checked_cast::<T>().unwrap()`.
    #[inline(always)]
    fn strict_cast<T: CheckedCastFromInt<Self>>(self) -> T {
        T::strict_cast_from(self)
    }

    /// Equivalent to `self.checked_cast::<T>().unwrap_unchecked()`.
    ///
    /// # Safety
    ///
    /// This results in undefined behavior when `self` will overflow when
    /// converted to the target type.
    #[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> {}

    // Cast<T> : SealedCast<T> avoids the orphan rule, which would otherwise
    // allow e.g. implementing Cast<Foo> for u8.
    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]);

/// Infallible conversion between integers.
pub trait CastFromInt<T>: SealedCast<T> {
    /// Converts `value` to this type.
    fn cast_from(value: T) -> Self;
}

/// Conversion between integers, wrapping around or saturating at the target type's boundaries.
pub trait BoundedCastFromInt<T>: SealedCast<T> {
    /// Converts `value` to this type, wrapping around at the boundary of the type.
    fn wrapping_cast_from(value: T) -> Self;

    /// Converts `value` to this type, saturating at the numeric bounds instead of overflowing.
    fn saturating_cast_from(value: T) -> Self;
}

/// Fallible conversion between integers.
pub trait CheckedCastFromInt<T>: SealedCast<T> {
    /// Converts `value` to this type, returning `None` if overflow would have occurred.
    fn checked_cast_from(value: T) -> Option<Self>;

    /// Converts `value` to this type, assuming overflow cannot occur.
    ///
    /// # Safety
    ///
    /// This results in undefined behavior when `value` will overflow when
    /// converted to this type.
    #[inline(always)]
    unsafe fn unchecked_cast_from(value: T) -> Self {
        unsafe { Self::checked_cast_from(value).unwrap_unchecked() }
    }

    /// Converts `value` to this type, panicking on overflow.
    ///
    /// # Panics
    ///
    /// This function will always panic on overflow, regardless of whether overflow checks are enabled.
    #[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]);

// Blanket implementations for NonZero source types. If the destination is a NonZero type it has a
// concrete implementation above.
#[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() {
        // Just a quick sanity check the (blanket) implementation works.
        assert_eq!(NonZeroU8::new(4).unwrap().cast::<i16>(), 42_i16);
        assert_eq!(
            NonZeroU8::new(4).unwrap().cast::<NonZeroI16>().get(),
            42_i16
        );
    }

    // All (negative) integers which are at or near a power of two to test
    // boundary conditions.
    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
        ]
    );
}