//! Identifier newtype primitives and helpers for defining and working with //! them. use core::hash::Hash; #[cfg(feature = "core-fmt")] use core::fmt; #[cfg(feature = "core-error")] use core::error; use crate::utilities::{CheckedArithmeticExt, IntegerOverflowError}; /// Constructs a new [`Identifier`] type. /// /// ```rust /// # #![feature(const_trait_impl)] /// # #![feature(const_default)] /// # #![feature(const_result_trait_fn)] /// # #![feature(const_convert)] /// # use azimuth_core::make_id; /// /// make_id!(pub Thing, "Some documentation about this `Identifier`"); /// ``` /// /// This generates a newtype `Thing` implementing `Identifier` and several /// conversion helpers. Alternatively, if you want to choose the underlying /// representation, specify a type implementing `Integer` like this: /// /// ```rust /// # #![feature(const_trait_impl)] /// # #![feature(const_default)] /// # #![feature(const_result_trait_fn)] /// # #![feature(const_convert)] /// # use azimuth_core::make_id; /// /// make_id!(pub Thing(u16), "Some documentation about this `Identifier`"); /// ``` #[macro_export] macro_rules! make_id { ($scope:vis $name:ident, $documentation:expr) => { make_id!($scope $name(usize), $documentation); }; ($scope:vis $name:ident($integer:tt), $documentation:expr) => { #[repr(transparent)] #[doc = $documentation] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "core-fmt", derive(Debug))] $scope struct $name(pub(crate) $integer); //SAFETY: we don't alter the structure of the underlying `Integer` // implementation in any way unsafe impl const $crate::id::Identifier for $name { type Integer = $integer; fn new(v: Self::Integer) -> Self { Self(v) } fn from_usize(v: usize) -> core::result::Result { <$integer as $crate::id::Integer>::from_usize(v).map(Self) } fn into_representation(self) -> Self::Integer { self.0 } fn into_usize(self) -> usize { <$integer as $crate::id::Integer>::into_usize(self.0) } fn increment(self) -> core::result::Result { <$integer as $crate::id::Integer>::increment(self.0).map(Self) } fn decrement(self) -> core::result::Result { <$integer as $crate::id::Integer>::decrement(self.0).map(Self) } } $crate::make_id!(@try_from_usize $name, $integer); impl const Default for $name { fn default() -> Self { Self(<$integer as $crate::id::Integer>::ZERO) } } impl const From<$name> for $integer { fn from(id: $name) -> Self { id.0 } } impl const From<$integer> for $name { fn from(v: $integer) -> Self { Self(v) } } }; //NOTE: we ignore this for `usize` because the automatic `TryFrom` // implementation would conflict (@try_from_usize $_name:ident, usize) => {}; (@try_from_usize $name:ident, $integer:ty) => { impl const TryFrom for $name { type Error = $crate::id::UsizeTruncationError; fn try_from(v: usize) -> Result { ::from_usize(v) } } impl const From<$name> for usize { fn from(id: $name) -> Self { <$name as $crate::id::Identifier>::into_usize(id) } } }; } /// Dense numeric identifier representation. /// /// Most implementors of this should be defined using the `id` macro for /// consistency in their implementations. /// /// # Safety /// /// `Identifier::from_usize` and `Identifier::into_usize` must form an /// order isomorphism between the `Id: Identifier` and an initial segment of /// `usize`. /// /// Equivalently: /// /// 1. (Order consistency) For all `Id`s `a`, `b`: `a.cmp(&b) == /// a.into_usize().cmp(&b.into_usize())`. /// /// 2. (Inverse) /// - For all `Id`s `a`: `Id::from_usize(a.into_usize()) == Ok(a)`. /// - For all `usize`s `u`: if `Id::from_usize(u) == Ok(a)` then /// `a.into_usize() == u`. /// /// 3. (Initial segment) If for some `usize` `u`, /// `Id::from_usize(u).is_err()`, then for all `v >= u`, /// `Id::from_usize(v).is_err()`. pub const unsafe trait Identifier: Copy + Eq + Ord + Hash + Send + Sync { /// The backing integer type for this identifier. type Integer: Integer; /// Instantiate a new identifier from its underlying representation. #[must_use] fn new(v: Self::Integer) -> Self; /// Instantiate a new identifier from a `usize`. /// /// # Errors /// /// Returns an error if the `usize` is too large to fit in the underlying /// representation. fn from_usize(v: usize) -> Result; /// Decompose this identifier into its underlying representation. #[must_use] fn into_representation(self) -> Self::Integer; /// Decompose this identifier into a `usize`. #[must_use] fn into_usize(self) -> usize; /// Increment the underlying value of this identifier. /// /// # Errors /// /// Returns an error if the result would have overflowed. fn increment(self) -> Result; /// Decrement the underlying value of this identifier. /// /// # Errors /// /// Returns an error if the result would have overflowed. fn decrement(self) -> Result; } //SAFETY: this doesn't alter the structure of the underlying `Integer` // implementation in any way unsafe impl const Identifier for T where T: const Integer, { type Integer = T; fn new(v: Self::Integer) -> Self { v } fn from_usize(v: usize) -> Result { T::from_usize(v) } fn into_representation(self) -> Self::Integer { self } fn into_usize(self) -> usize { ::into_usize(self) } fn increment(self) -> Result { ::increment(self) } fn decrement(self) -> Result { ::decrement(self) } } //NOTE: we don't use the same [`Integer`] trait in `numerics` here as there // is specific behavior desired in an "integer backing an id"---such as // checked overflow---that may not be wanted in more general arithmetic // operations. /// Valid integer types backing an [`Identifier`]. pub const trait Integer: Copy + Eq + Ord + Hash + Send + Sync + CheckedArithmeticExt + sealed::Sealed { /// Zero value of the integer type. const ZERO: Self; /// Converts this integer into a `usize`. fn into_usize(self) -> usize; /// Constructs an integer from a `usize`. /// /// # Errors /// /// Returns an error if the `usize` is too large to fit in the integer. fn from_usize(v: usize) -> Result; /// Increments the value of this integer. /// /// # Errors /// /// Returns an error if the result would have overflowed. fn increment(self) -> Result; /// Decrements the value of this integer. /// /// # Errors /// /// Returns an error if the result would have overflowed. fn decrement(self) -> Result; } macro_rules! integer_impl { ($t:tt) => { impl sealed::Sealed for $t {} impl const Integer for $t { const ZERO: Self = 0; integer_impl!(@usize_specialization $t); fn increment(self) -> Result { self.errored_increment() } fn decrement(self) -> Result { self.errored_decrement() } } }; (@usize_specialization usize) => { fn into_usize(self) -> usize { self } fn from_usize(v: usize) -> Result { Ok(v) } }; (@usize_specialization $t:tt) => { #[allow(clippy::cast_possible_truncation)] fn into_usize(self) -> usize { //NOTE: truncation never happens as `Integer` implementations // are gated behind `target_pointer_width`. self as usize } #[allow(clippy::cast_possible_truncation)] fn from_usize(v: usize) -> Result { //NOTE: truncation can't happen due to pointer width gating. if v <= Self::MAX as usize { //NOTE: we just checked this exists and doesn't truncate. Ok(v as $t) } else { Err(UsizeTruncationError) } } }; } integer_impl!(usize); #[cfg(target_pointer_width = "64")] integer_impl!(u64); #[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] integer_impl!(u32); integer_impl!(u16); integer_impl!(u8); /// Representation of an error caused by a conversion to an integer from a /// `usize`. #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "core-fmt", derive(Debug))] pub struct UsizeTruncationError; #[cfg(feature = "core-fmt")] impl fmt::Display for UsizeTruncationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("usize truncation") } } #[cfg(feature = "core-error")] impl error::Error for UsizeTruncationError {} mod sealed { pub trait Sealed {} }