Skip to content

Commit 4a34b7e

Browse files
authored
Merge pull request #5 from cuviper/as
Add `PrimitiveNumber` methods for `as` type-casting
2 parents 2314842 + 2b0290c commit 4a34b7e

2 files changed

Lines changed: 126 additions & 6 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,6 @@ mod tests;
7272
pub use self::error::PrimitiveError;
7373
pub use self::float::{PrimitiveFloat, PrimitiveFloatRef, PrimitiveFloatToInt};
7474
pub use self::integer::{PrimitiveInteger, PrimitiveIntegerRef};
75-
pub use self::number::{PrimitiveNumber, PrimitiveNumberRef};
75+
pub use self::number::{PrimitiveNumber, PrimitiveNumberAs, PrimitiveNumberRef};
7676
pub use self::signed::{PrimitiveSigned, PrimitiveSignedRef};
7777
pub use self::unsigned::{PrimitiveUnsigned, PrimitiveUnsignedRef};

src/number.rs

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::PrimitiveError;
22

33
trait Sealed {}
4+
struct SealedToken;
45

56
/// Trait for all primitive [numeric types].
67
///
@@ -45,6 +46,20 @@ trait Sealed {}
4546
pub trait PrimitiveNumber:
4647
'static
4748
+ Sealed
49+
+ PrimitiveNumberAs<f32>
50+
+ PrimitiveNumberAs<f64>
51+
+ PrimitiveNumberAs<i8>
52+
+ PrimitiveNumberAs<i16>
53+
+ PrimitiveNumberAs<i32>
54+
+ PrimitiveNumberAs<i64>
55+
+ PrimitiveNumberAs<i128>
56+
+ PrimitiveNumberAs<isize>
57+
+ PrimitiveNumberAs<u8>
58+
+ PrimitiveNumberAs<u16>
59+
+ PrimitiveNumberAs<u32>
60+
+ PrimitiveNumberAs<u64>
61+
+ PrimitiveNumberAs<u128>
62+
+ PrimitiveNumberAs<usize>
4863
+ core::cmp::PartialEq
4964
+ core::cmp::PartialOrd
5065
+ core::convert::From<bool>
@@ -89,13 +104,13 @@ pub trait PrimitiveNumber:
89104
/// [`to_be_bytes`][Self::to_be_bytes]. It is effectively `[u8; size_of::<Self>()]`.
90105
type Bytes: core::borrow::Borrow<[u8]> + core::borrow::BorrowMut<[u8]>;
91106

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

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

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

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

107122
/// Returns the memory representation of this number as a byte array in native-endian order.
108123
fn to_ne_bytes(self) -> Self::Bytes;
124+
125+
/// Creates a number using a type cast, `value as Self`.
126+
///
127+
/// Note: unlike other `num-primitive` methods, there is no inherent method by this name on the
128+
/// actual types.
129+
fn as_from<T>(value: T) -> Self
130+
where
131+
Self: PrimitiveNumberAs<T>,
132+
{
133+
<Self as PrimitiveNumberAs<T>>::__as_from(value, SealedToken)
134+
}
135+
136+
/// Converts this number with a type cast, `self as T`.
137+
///
138+
/// Note: unlike other `num-primitive` methods, there is no inherent method by this name on the
139+
/// actual types.
140+
fn as_to<T>(self) -> T
141+
where
142+
Self: PrimitiveNumberAs<T>,
143+
{
144+
<Self as PrimitiveNumberAs<T>>::__as_to(self, SealedToken)
145+
}
109146
}
110147

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

182+
/// Trait for numeric conversions supported by the [`as`] keyword.
183+
///
184+
/// This is effectively the same as the [type cast] expression `self as N`, implemented for all
185+
/// combinations of [`PrimitiveNumber`].
186+
///
187+
/// [`as`]: https://doc.rust-lang.org/std/keyword.as.html
188+
/// [type cast]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions
189+
///
190+
/// # Examples
191+
///
192+
/// `PrimitiveNumberAs<{number}>` is a supertrait of [`PrimitiveNumber`] for all primitive floats
193+
/// and integers, so you do not need to use this trait directly when converting concrete types.
194+
///
195+
/// ```
196+
/// use num_primitive::PrimitiveNumber;
197+
///
198+
/// // Clamp any number to the interval 0..=100, unless it is NaN.
199+
/// fn clamp_percentage<Number: PrimitiveNumber>(x: Number) -> Number {
200+
/// let clamped = x.as_to::<f64>().clamp(0.0, 100.0);
201+
/// Number::as_from(clamped)
202+
/// }
203+
///
204+
/// assert_eq!(clamp_percentage(-42_i8), 0_i8);
205+
/// assert_eq!(clamp_percentage(42_u128), 42_u128);
206+
/// assert_eq!(clamp_percentage(1e100_f64), 100_f64);
207+
/// assert!(clamp_percentage(f32::NAN).is_nan());
208+
/// ```
209+
///
210+
/// However, if the other type is also generic, an explicit type constraint is needed.
211+
///
212+
/// ```
213+
/// use num_primitive::{PrimitiveNumber, PrimitiveNumberAs};
214+
///
215+
/// fn clamp_any<Number, Limit>(x: Number, min: Limit, max: Limit) -> Number
216+
/// where
217+
/// Number: PrimitiveNumber + PrimitiveNumberAs<Limit>,
218+
/// Limit: PartialOrd,
219+
/// {
220+
/// assert!(min <= max);
221+
/// let y = x.as_to::<Limit>();
222+
/// if y <= min {
223+
/// Number::as_from(min)
224+
/// } else if y >= max {
225+
/// Number::as_from(max)
226+
/// } else {
227+
/// x
228+
/// }
229+
/// }
230+
///
231+
/// assert_eq!(clamp_any(1.23, 0_i8, 10_i8), 1.23);
232+
/// assert_eq!(clamp_any(1.23, -1_i8, 1_i8), 1.0);
233+
/// assert_eq!(clamp_any(i128::MAX, 0.0, 1e100), i128::MAX);
234+
/// ```
235+
pub trait PrimitiveNumberAs<T> {
236+
#[doc(hidden)]
237+
#[expect(private_interfaces)]
238+
fn __as_from(x: T, _: SealedToken) -> Self;
239+
240+
#[doc(hidden)]
241+
#[expect(private_interfaces)]
242+
fn __as_to(x: Self, _: SealedToken) -> T;
243+
}
244+
145245
macro_rules! impl_primitive {
146-
($($Number:ident),*) => {$(
246+
($($Number:ident),+) => {$(
147247
impl Sealed for $Number {}
148248
impl Sealed for &$Number {}
149249

@@ -163,7 +263,27 @@ macro_rules! impl_primitive {
163263
}
164264

165265
impl PrimitiveNumberRef<$Number> for &$Number {}
166-
)*}
266+
267+
impl_primitive!($Number as f32, f64);
268+
impl_primitive!($Number as i8, i16, i32, i64, i128, isize);
269+
impl_primitive!($Number as u8, u16, u32, u64, u128, usize);
270+
)+};
271+
272+
($Number:ident as $($Other:ident),+) => {$(
273+
impl PrimitiveNumberAs<$Other> for $Number {
274+
#[inline]
275+
#[expect(private_interfaces)]
276+
fn __as_from(x: $Other, _: SealedToken) -> Self {
277+
x as Self
278+
}
279+
280+
#[inline]
281+
#[expect(private_interfaces)]
282+
fn __as_to(x: Self, _: SealedToken) -> $Other {
283+
x as $Other
284+
}
285+
}
286+
)+}
167287
}
168288

169289
impl_primitive!(f32, f64);

0 commit comments

Comments
 (0)