From d6038d7116470098e783c04749f5068fcdda3e15 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Fri, 10 Nov 2023 20:09:21 -0500 Subject: [PATCH] Require unsafe internally, expose `new_saturating` --- .github/workflows/build.yaml | 2 +- Cargo.toml | 2 +- src/lib.rs | 323 +++++++++++++++++------------------ src/unsafe_wrapper.rs | 26 +++ 4 files changed, 186 insertions(+), 167 deletions(-) create mode 100644 src/unsafe_wrapper.rs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 320233d..784395e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: strategy: matrix: rust: - - { version: "1.65.0", name: MSRV } + - { version: "1.67.0", name: MSRV } - { version: stable, name: stable } steps: diff --git a/Cargo.toml b/Cargo.toml index f99ed00..f1a836d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "deranged" version = "0.3.9" authors = ["Jacob Pratt "] edition = "2021" -rust-version = "1.65.0" +rust-version = "1.67.0" repository = "https://github.com/jhpratt/deranged" keywords = ["integer", "int", "range"] readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 441dacb..2d2b350 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ #[cfg(test)] mod tests; mod traits; +mod unsafe_wrapper; #[cfg(feature = "alloc")] #[allow(unused_extern_crates)] @@ -59,6 +60,8 @@ use std::error::Error; #[cfg(feature = "powerfmt")] use powerfmt::smart_display; +use crate::unsafe_wrapper::Unsafe; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TryFromIntError; @@ -162,159 +165,122 @@ macro_rules! impl_ranged { optional: $optional_type:ident } )*) => {$( - pub use $mod_name::{$type, $optional_type}; + #[doc = concat!( + article!($is_signed), + " `", + stringify!($internal), + "` that is known to be in the range `MIN..=MAX`.", + )] + #[repr(transparent)] + #[derive(Clone, Copy, Eq, Ord, Hash)] + pub struct $type( + Unsafe<$internal>, + ); + + #[doc = concat!( + "A `", + stringify!($type), + "` that is optional. Equivalent to [`Option<", + stringify!($type), + ">`] with niche value optimization.", + )] + /// + #[doc = concat!( + "If `MIN` is [`", + stringify!($internal), + "::MIN`] _and_ `MAX` is [`", + stringify!($internal) + ,"::MAX`] then compilation will fail. This is because there is no way to represent \ + the niche value.", + )] + /// + /// This type is useful when you need to store an optional ranged value in a struct, but + /// do not want the overhead of an `Option` type. This reduces the size of the struct + /// overall, and is particularly useful when you have a large number of optional fields. + /// Note that most operations must still be performed on the [`Option`] type, which is + #[doc = concat!("obtained with [`", stringify!($optional_type), "::get`].")] + #[repr(transparent)] + #[derive(Clone, Copy, Eq, Hash)] + pub struct $optional_type( + $internal, + ); - // Introduce the type in a module. This ensures that all accesses and mutations of the field - // have the necessary checks. - mod $mod_name { - #[doc = concat!( - article!($is_signed), - " `", - stringify!($internal), - "` that is known to be in the range `MIN..=MAX`.", - )] - #[repr(transparent)] - #[derive(Clone, Copy, Eq, Ord, Hash)] - pub struct $type( - $internal, - ); + impl $type { + /// The smallest value that can be represented by this type. + // Safety: `MIN` is in range by definition. + pub const MIN: Self = Self::new_static::(); - #[doc = concat!( - "A `", - stringify!($type), - "` that is optional. Equivalent to [`Option<", - stringify!($type), - ">`] with niche value optimization.", - )] + /// The largest value that can be represented by this type. + // Safety: `MAX` is in range by definition. + pub const MAX: Self = Self::new_static::(); + + /// Creates a ranged integer without checking the value. /// - #[doc = concat!( - "If `MIN` is [`", - stringify!($internal), - "::MIN`] _and_ `MAX` is [`", - stringify!($internal) - ,"::MAX`] then compilation will fail. This is because there is no way to represent \ - the niche value.", - )] + /// # Safety /// - /// This type is useful when you need to store an optional ranged value in a struct, but - /// do not want the overhead of an `Option` type. This reduces the size of the struct - /// overall, and is particularly useful when you have a large number of optional fields. - /// Note that most operations must still be performed on the [`Option`] type, which is - #[doc = concat!("obtained with [`", stringify!($optional_type), "::get`].")] - #[repr(transparent)] - #[derive(Clone, Copy, Eq, Hash)] - pub struct $optional_type( - $internal, - ); - - impl $type { - /// Creates a ranged integer without checking the value. - /// - /// # Safety - /// - /// The value must be within the range `MIN..=MAX`. - #[inline(always)] - pub const unsafe fn new_unchecked(value: $internal) -> Self { - ::ASSERT; - // Safety: The caller must ensure that the value is in range. - unsafe { $crate::assume(MIN <= value && value <= MAX) }; - Self(value) - } - - /// Creates a ranged integer if the given value is in the range - /// `MIN..=MAX`. - #[inline(always)] - pub const fn new(value: $internal) -> Option { - ::ASSERT; - if value < MIN || value > MAX { - None - } else { - Some(Self(value)) - } - } - - /// Returns the value as a primitive type. - #[inline(always)] - pub const fn get(self) -> $internal { - ::ASSERT; - // Safety: A stored value is always in range. - unsafe { $crate::assume(MIN <= self.0 && self.0 <= MAX) }; - self.0 - } - - #[inline(always)] - pub(crate) const fn get_ref(&self) -> &$internal { - ::ASSERT; - // Safety: A stored value is always in range. - unsafe { $crate::assume(MIN <= self.0 && self.0 <= MAX) }; - &self.0 + /// The value must be within the range `MIN..=MAX`. + #[inline(always)] + pub const unsafe fn new_unchecked(value: $internal) -> Self { + ::ASSERT; + // Safety: The caller must ensure that the value is in range. + unsafe { + $crate::assume(MIN <= value && value <= MAX); + Self(Unsafe::new(value)) } } - impl $optional_type { - /// The value used as the niche. Must not be in the range `MIN..=MAX`. - const NICHE: $internal = match (MIN, MAX) { - ($internal::MIN, $internal::MAX) => panic!("type has no niche"), - ($internal::MIN, _) => $internal::MAX, - (_, _) => $internal::MIN, - }; - - /// An optional ranged value that is not present. - #[allow(non_upper_case_globals)] - pub const None: Self = Self(Self::NICHE); + /// Returns the value as a primitive type. + #[inline(always)] + pub const fn get(self) -> $internal { + ::ASSERT; + // Safety: A stored value is always in range. + unsafe { $crate::assume(MIN <= *self.0.get() && *self.0.get() <= MAX) }; + *self.0.get() + } - /// Creates an optional ranged value that is present. - #[allow(non_snake_case)] - #[inline(always)] - pub const fn Some(value: $type) -> Self { - <$type as $crate::traits::RangeIsValid>::ASSERT; - Self(value.get()) - } + #[inline(always)] + pub(crate) const fn get_ref(&self) -> &$internal { + ::ASSERT; + let value = self.0.get(); + // Safety: A stored value is always in range. + unsafe { $crate::assume(MIN <= *value && *value <= MAX) }; + value + } - /// Returns the value as the standard library's [`Option`] type. - #[inline(always)] - pub const fn get(self) -> Option<$type> { - <$type as $crate::traits::RangeIsValid>::ASSERT; - if self.0 == Self::NICHE { - None - } else { - // Safety: A stored value that is not the niche is always in range. - Some(unsafe { $type::new_unchecked(self.0) }) - } + /// Creates a ranged integer if the given value is in the range `MIN..=MAX`. + #[inline(always)] + pub const fn new(value: $internal) -> Option { + ::ASSERT; + if value < MIN || value > MAX { + None + } else { + // Safety: The value is in range. + Some(unsafe { Self::new_unchecked(value) }) } + } - /// Creates an optional ranged integer without checking the value. - /// - /// # Safety - /// - /// The value must be within the range `MIN..=MAX`. As the value used for niche - /// value optimization is unspecified, the provided value must not be the niche - /// value. - #[inline(always)] - pub const unsafe fn some_unchecked(value: $internal) -> Self { - <$type as $crate::traits::RangeIsValid>::ASSERT; - // Safety: The caller must ensure that the value is in range. - unsafe { $crate::assume(MIN <= value && value <= MAX) }; - Self(value) - } + /// Creates a ranged integer with a statically known value. **Fails to compile** if the + /// value is not in range. + #[inline(always)] + pub const fn new_static() -> Self { + <($type, $type) as $crate::traits::StaticIsValid>::ASSERT; + // Safety: The value is in range. + unsafe { Self::new_unchecked(VALUE) } + } - /// Obtain the inner value of the struct. This is useful for comparisons. - #[inline(always)] - pub(crate) const fn inner(self) -> $internal { - <$type as $crate::traits::RangeIsValid>::ASSERT; - self.0 + /// Creates a ranged integer with the given value, saturating if it is out of range. + #[inline] + pub const fn new_saturating(value: $internal) -> Self { + ::ASSERT; + if value < MIN { + Self::MIN + } else if value > MAX { + Self::MAX + } else { + // Safety: The value is in range. + unsafe { Self::new_unchecked(value) } } } - } - - impl $type { - /// The smallest value that can be represented by this type. - // Safety: `MIN` is in range by definition. - pub const MIN: Self = unsafe { Self::new_unchecked(MIN) }; - - /// The largest value that can be represented by this type. - // Safety: `MAX` is in range by definition. - pub const MAX: Self = unsafe { Self::new_unchecked(MAX) }; /// Expand the range that the value may be in. **Fails to compile** if the new range is /// not a superset of the current range. @@ -343,32 +309,6 @@ macro_rules! impl_ranged { $type::::new(self.get()) } - - /// Creates a ranged integer with a statically known value. **Fails to compile** if the - /// value is not in range. - #[inline(always)] - pub const fn new_static() -> Self { - <($type, $type) as $crate::traits::StaticIsValid>::ASSERT; - // Safety: The value is in range. - unsafe { Self::new_unchecked(VALUE) } - } - - #[inline] - const fn new_saturating(value: $internal) -> Self { - ::ASSERT; - // Safety: The value is clamped to the range. - unsafe { - Self::new_unchecked(if value < MIN { - MIN - } else if value > MAX { - MAX - } else { - value - }) - } - } - - /// Converts a string slice in a given base to an integer. /// /// The string is expected to be an optional `+` or `-` sign followed by digits. Leading @@ -863,6 +803,59 @@ macro_rules! impl_ranged { } impl $optional_type { + /// The value used as the niche. Must not be in the range `MIN..=MAX`. + const NICHE: $internal = match (MIN, MAX) { + ($internal::MIN, $internal::MAX) => panic!("type has no niche"), + ($internal::MIN, _) => $internal::MAX, + (_, _) => $internal::MIN, + }; + + /// An optional ranged value that is not present. + #[allow(non_upper_case_globals)] + pub const None: Self = Self(Self::NICHE); + + /// Creates an optional ranged value that is present. + #[allow(non_snake_case)] + #[inline(always)] + pub const fn Some(value: $type) -> Self { + <$type as $crate::traits::RangeIsValid>::ASSERT; + Self(value.get()) + } + + /// Returns the value as the standard library's [`Option`] type. + #[inline(always)] + pub const fn get(self) -> Option<$type> { + <$type as $crate::traits::RangeIsValid>::ASSERT; + if self.0 == Self::NICHE { + None + } else { + // Safety: A stored value that is not the niche is always in range. + Some(unsafe { $type::new_unchecked(self.0) }) + } + } + + /// Creates an optional ranged integer without checking the value. + /// + /// # Safety + /// + /// The value must be within the range `MIN..=MAX`. As the value used for niche + /// value optimization is unspecified, the provided value must not be the niche + /// value. + #[inline(always)] + pub const unsafe fn some_unchecked(value: $internal) -> Self { + <$type as $crate::traits::RangeIsValid>::ASSERT; + // Safety: The caller must ensure that the value is in range. + unsafe { $crate::assume(MIN <= value && value <= MAX) }; + Self(value) + } + + /// Obtain the inner value of the struct. This is useful for comparisons. + #[inline(always)] + pub(crate) const fn inner(self) -> $internal { + <$type as $crate::traits::RangeIsValid>::ASSERT; + self.0 + } + #[inline(always)] pub const fn get_primitive(self) -> Option<$internal> { <$type as $crate::traits::RangeIsValid>::ASSERT; diff --git a/src/unsafe_wrapper.rs b/src/unsafe_wrapper.rs new file mode 100644 index 0000000..8620e12 --- /dev/null +++ b/src/unsafe_wrapper.rs @@ -0,0 +1,26 @@ +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Unsafe(T); + +impl core::fmt::Debug for Unsafe { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl Unsafe { + pub(crate) const unsafe fn new(value: T) -> Self { + Self(value) + } + + pub(crate) const fn get(&self) -> &T { + &self.0 + } +} + +impl core::ops::Deref for Unsafe { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +}