Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ mod tests;
pub use self::error::PrimitiveError;
pub use self::float::{PrimitiveFloat, PrimitiveFloatRef, PrimitiveFloatToInt};
pub use self::integer::{PrimitiveInteger, PrimitiveIntegerRef};
pub use self::number::{PrimitiveNumber, PrimitiveNumberRef};
pub use self::number::{PrimitiveNumber, PrimitiveNumberAs, PrimitiveNumberRef};
pub use self::signed::{PrimitiveSigned, PrimitiveSignedRef};
pub use self::unsigned::{PrimitiveUnsigned, PrimitiveUnsignedRef};
130 changes: 125 additions & 5 deletions src/number.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::PrimitiveError;

trait Sealed {}
struct SealedToken;

/// Trait for all primitive [numeric types].
///
Expand Down Expand Up @@ -45,6 +46,20 @@ trait Sealed {}
pub trait PrimitiveNumber:
'static
+ Sealed
+ PrimitiveNumberAs<f32>
+ PrimitiveNumberAs<f64>
+ PrimitiveNumberAs<i8>
+ PrimitiveNumberAs<i16>
+ PrimitiveNumberAs<i32>
+ PrimitiveNumberAs<i64>
+ PrimitiveNumberAs<i128>
+ PrimitiveNumberAs<isize>
+ PrimitiveNumberAs<u8>
+ PrimitiveNumberAs<u16>
+ PrimitiveNumberAs<u32>
+ PrimitiveNumberAs<u64>
+ PrimitiveNumberAs<u128>
+ PrimitiveNumberAs<usize>
+ core::cmp::PartialEq
+ core::cmp::PartialOrd
+ core::convert::From<bool>
Expand Down Expand Up @@ -89,13 +104,13 @@ pub trait PrimitiveNumber:
/// [`to_be_bytes`][Self::to_be_bytes]. It is effectively `[u8; size_of::<Self>()]`.
type Bytes: core::borrow::Borrow<[u8]> + core::borrow::BorrowMut<[u8]>;

/// Creates a value from its representation as a byte array in big endian.
/// Creates a number from its representation as a byte array in big endian.
fn from_be_bytes(bytes: Self::Bytes) -> Self;

/// Creates a value from its representation as a byte array in little endian.
/// Creates a number from its representation as a byte array in little endian.
fn from_le_bytes(bytes: Self::Bytes) -> Self;

/// Creates a value from its representation as a byte array in native endian.
/// Creates a number from its representation as a byte array in native endian.
fn from_ne_bytes(bytes: Self::Bytes) -> Self;

/// Returns the memory representation of this number as a byte array in little-endian order.
Expand All @@ -106,6 +121,28 @@ pub trait PrimitiveNumber:

/// Returns the memory representation of this number as a byte array in native-endian order.
fn to_ne_bytes(self) -> Self::Bytes;

/// Creates a number using a type cast, `value as Self`.
///
/// Note: unlike other `num-primitive` methods, there is no inherent method by this name on the
/// actual types.
fn as_from<T>(value: T) -> Self
where
Self: PrimitiveNumberAs<T>,
{
<Self as PrimitiveNumberAs<T>>::__as_from(value, SealedToken)
}

/// Converts this number with a type cast, `self as T`.
///
/// Note: unlike other `num-primitive` methods, there is no inherent method by this name on the
/// actual types.
fn as_to<T>(self) -> T
where
Self: PrimitiveNumberAs<T>,
{
<Self as PrimitiveNumberAs<T>>::__as_to(self, SealedToken)
}
}

/// Trait for references to primitive numbers ([`PrimitiveNumber`]).
Expand Down Expand Up @@ -142,8 +179,71 @@ pub trait PrimitiveNumberRef<T>:
{
}

/// Trait for numeric conversions supported by the [`as`] keyword.
///
/// This is effectively the same as the [type cast] expression `self as N`, implemented for all
/// combinations of [`PrimitiveNumber`].
///
/// [`as`]: https://doc.rust-lang.org/std/keyword.as.html
/// [type cast]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions
///
/// # Examples
///
/// `PrimitiveNumberAs<{number}>` is a supertrait of [`PrimitiveNumber`] for all primitive floats
/// and integers, so you do not need to use this trait directly when converting concrete types.
///
/// ```
/// use num_primitive::PrimitiveNumber;
///
/// // Clamp any number to the interval 0..=100, unless it is NaN.
/// fn clamp_percentage<Number: PrimitiveNumber>(x: Number) -> Number {
/// let clamped = x.as_to::<f64>().clamp(0.0, 100.0);
/// Number::as_from(clamped)
/// }
///
/// assert_eq!(clamp_percentage(-42_i8), 0_i8);
/// assert_eq!(clamp_percentage(42_u128), 42_u128);
/// assert_eq!(clamp_percentage(1e100_f64), 100_f64);
/// assert!(clamp_percentage(f32::NAN).is_nan());
/// ```
///
/// However, if the other type is also generic, an explicit type constraint is needed.
///
/// ```
/// use num_primitive::{PrimitiveNumber, PrimitiveNumberAs};
///
/// fn clamp_any<Number, Limit>(x: Number, min: Limit, max: Limit) -> Number
/// where
/// Number: PrimitiveNumber + PrimitiveNumberAs<Limit>,
/// Limit: PartialOrd,
/// {
/// assert!(min <= max);
/// let y = x.as_to::<Limit>();
/// if y <= min {
/// Number::as_from(min)
/// } else if y >= max {
/// Number::as_from(max)
/// } else {
/// x
/// }
/// }
///
/// assert_eq!(clamp_any(1.23, 0_i8, 10_i8), 1.23);
/// assert_eq!(clamp_any(1.23, -1_i8, 1_i8), 1.0);
/// assert_eq!(clamp_any(i128::MAX, 0.0, 1e100), i128::MAX);
/// ```
pub trait PrimitiveNumberAs<T> {
#[doc(hidden)]
#[expect(private_interfaces)]
fn __as_from(x: T, _: SealedToken) -> Self;

#[doc(hidden)]
#[expect(private_interfaces)]
fn __as_to(x: Self, _: SealedToken) -> T;
}

macro_rules! impl_primitive {
($($Number:ident),*) => {$(
($($Number:ident),+) => {$(
impl Sealed for $Number {}
impl Sealed for &$Number {}

Expand All @@ -163,7 +263,27 @@ macro_rules! impl_primitive {
}

impl PrimitiveNumberRef<$Number> for &$Number {}
)*}

impl_primitive!($Number as f32, f64);
impl_primitive!($Number as i8, i16, i32, i64, i128, isize);
impl_primitive!($Number as u8, u16, u32, u64, u128, usize);
)+};

($Number:ident as $($Other:ident),+) => {$(
impl PrimitiveNumberAs<$Other> for $Number {
#[inline]
#[expect(private_interfaces)]
fn __as_from(x: $Other, _: SealedToken) -> Self {
x as Self
}

#[inline]
#[expect(private_interfaces)]
fn __as_to(x: Self, _: SealedToken) -> $Other {
x as $Other
}
}
)+}
}

impl_primitive!(f32, f64);
Expand Down