From c882ad994560d798b4ed316692572db8e2112a0b Mon Sep 17 00:00:00 2001 From: Techcable Date: Fri, 30 Aug 2024 14:59:48 -0700 Subject: [PATCH] Add unwrap_cast method for checked casts This method is NOT something that is present in the standard library, or part of `strict_overflow_ops` feature Also add checked_cast method for parity with stdlib methods. --- Cargo.toml | 2 +- src/lib.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f49b3c7..51ef6ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "unwrap-overflow-ops" description = "arithmetic operations that always panic on overflow" repository = "https://github.com/Techcable/unwrap-overflow-ops.rust" license = "Apache-2.0 OR MIT" -version = "0.1.0" +version = "0.1.1" edition = "2021" readme = "README.md" categories = ["mathematics", "no-std", "no-std::no-alloc", "rust-patterns"] diff --git a/src/lib.rs b/src/lib.rs index ce9e0a3..4769a0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ #![no_std] use core::fmt::Debug; +use paste::paste; + macro_rules! _stringify_or_default { (default: $default:tt; $val:ident) => { stringify!($val) @@ -80,6 +82,40 @@ macro_rules! unwrap_num_ops { )*}); } +/// Marker type for a primitive integer. +/// +/// ## Safety +/// Guarenteed to be one of the builtin primitive integer types. +/// +/// Methods are guarenteed to be implemented correctly. +pub unsafe trait PrimInt: + Copy + Debug + Sized + Eq + PartialEq + internal::PrimIntInternal + sealed::Sealed +{ + /// Attempt to cast this integer into another type, + /// returning `None` if overflow occurs. + /// + /// This is equivalent to invoking the appropriate [`TryFrom`] implementation, + /// but returns an `Option` for consistency with the checked arithmetic methods. + #[inline] + #[must_use] + fn checked_cast(self) -> Option { + use self::internal::PrimIntInternal; + macro_rules! match_types { + ($src:expr, $dest:ident => $($size:literal),+) => (paste! { + match ($dest::BITS, $dest::SIGNED) { + $( + ($size, false) => $src.[]()?.bit_cast::<$dest>(), + ($size, true) => $src.[]()?.bit_cast::<$dest>(), + )* + _ => unreachable!(), + } + }); + + } + Some(match_types!(self, T => 8, 16, 32, 64, 128)) + } +} + /// An extension trait for arithmetic operations /// that are guaranteed to panic on overflow. /// @@ -94,7 +130,7 @@ macro_rules! unwrap_num_ops { /// regardless of compiler settings and `cfg!(...)` flags. /// /// The correctness of these methods can be relied upon for memory safety. -pub unsafe trait UnwrapOverflowOps: Copy + Debug + Sized + sealed::Sealed { +pub unsafe trait UnwrapOverflowOps: PrimInt + sealed::Sealed { unwrap_num_ops! { add { arg: Self, @@ -153,6 +189,52 @@ pub unsafe trait UnwrapOverflowOps: Copy + Debug + Sized + sealed::Sealed { panic_example: "let _ = i32::MAX.unwrap_pow(2);", }, } + + /// Cast to another integer type, + /// panicking if overflow occurs. + /// + /// Unlike the most other methods in this crate, + /// there is not currently a similar method in the standard library. + /// Casts are not offered as part of the + /// [`strict_overflow_ops` feature](https://github.com/rust-lang/rust/issues/118260) + /// + /// See also [`PrimInt::checked_cast`], which returns a `None` on overflow. + /// + /// # Panics + /// This function will always panic on overflow. + /// + /// # Examples + /// Basic usage: + /// ``` + /// use unwrap_overflow_ops::*; + /// assert_eq!((-1i32).unwrap_cast::(), -1i8); + /// assert_eq!(i8::MIN.unwrap_cast::(), -128); + /// assert_eq!(i8::MAX.unwrap_cast::(), 127); + /// assert_eq!(i32::MAX.unwrap_cast::(), 2147483647u32); + /// ``` + /// + /// The following will all panic: + /// ```should_panic + /// use unwrap_overflow_ops::*; + /// let _ = (-1i32).unwrap_cast::(); + /// ``` + /// ```should_panic + /// use unwrap_overflow_ops::*; + /// let _ = i32::MIN.unwrap_cast::(); + /// ``` + /// ```should_panic + /// use unwrap_overflow_ops::*; + /// let _ = (255u8).unwrap_cast::(); + /// ``` + #[inline] + #[must_use] + #[track_caller] + fn unwrap_cast(self) -> T { + match self.checked_cast::() { + Some(val) => val, + None => self::overflow_ops::cast(), + } + } } /// An extension trait for signed arithmetic operations @@ -273,6 +355,7 @@ macro_rules! impl_signed_ints { } } } + unsafe impl PrimInt for [] {} impl sealed::Sealed for [] {} )* }); } @@ -287,11 +370,65 @@ macro_rules! impl_unsigned_ints { type Signed = []; common_methods_impl!(add_signed(Self::Signed) -> Self); } + unsafe impl PrimInt for [] {} impl sealed::Sealed for [] {} )* }) } impl_unsigned_ints!(8, 16, 32, 64, 128, size); +mod internal { + use paste::paste; + + macro_rules! foreach_as_method { + (do $mode:tt) => (foreach_as_method! { + do $mode; for u8, u16, u32, u64, u128, + usize, i8, i16, i32, i64, i128, isize + }); + (do impl; for $($target:ident),*) => (paste! {$( + #[inline] + fn [](self) -> Option<$target> { + $target::try_from(self).ok() + } + )*}); + (do declare; for $($target:ident),*) => (paste! {$( + #[must_use] + fn [](self) -> Option<$target>; + )*}); + } + + /// Internal methods for primitive integers. + /// + /// ## Safety + /// Guarenteed to be implemented correctly. + pub unsafe trait PrimIntInternal: Sized + Copy { + const SIGNED: bool; + const BITS: u32; + + #[inline] + fn bit_cast(self) -> T { + assert!(Self::BITS == T::BITS); + unsafe { core::mem::transmute_copy(&self) } + } + + foreach_as_method!(do declare); + } + macro_rules! impl_primint_internal { + () => { + impl_primint_internal!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); + }; + ($($target:ident),+) => ($( + unsafe impl PrimIntInternal for $target { + #[allow(unused_comparisons)] + const SIGNED: bool = <$target>::MIN < 0; + const BITS: u32 = <$target>::BITS; + + foreach_as_method!(do impl); + } + )*); + } + impl_primint_internal!(); +} + mod sealed { pub trait Sealed {} } @@ -321,6 +458,7 @@ mod overflow_ops { shr => "shift right", shl => "shift left", pow => "take integer power", + cast => "cast integer", } // alias used by macros