Skip to content

Commit

Permalink
Add more const constructors and various convenience functions to Numb…
Browse files Browse the repository at this point in the history
…er for generic conversions

In particular:
- new_u8, new_u16, ..., new_u128 which allow creating an arbitrary int without type conversion,
  e.g. `u5::new_u32(123)`
- new_() which allows any Number argument to be passed through generics
- as_() which easily converts any Number to another
- as_u8(), as_u16() for more control (and to implement the others)
  • Loading branch information
danlehmann committed Aug 25, 2024
1 parent 0a54994 commit 35b1079
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 1 deletion.
200 changes: 199 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ impl Display for TryNewError {

#[cfg_attr(feature = "const_convert_and_const_trait_impl", const_trait)]
pub trait Number: Sized {
type UnderlyingType: Debug
type UnderlyingType: Copy
+ Clone
+ Number
+ Debug
+ From<u8>
+ TryFrom<u16>
+ TryFrom<u32>
Expand All @@ -57,11 +60,32 @@ pub trait Number: Sized {
/// Maximum value that can be represented by this type
const MAX: Self;

/// Creates a number from the given value, throwing an error if the value is too large
fn new(value: Self::UnderlyingType) -> Self;

/// Creates a number from the given value, return None if the value is too large
fn try_new(value: Self::UnderlyingType) -> Result<Self, TryNewError>;

fn value(self) -> Self::UnderlyingType;

fn new_<T: Number>(value: T) -> Self;

fn masked_new<T: Number>(value: T) -> Self;

fn as_u8(&self) -> u8;

fn as_u16(&self) -> u16;

fn as_u32(&self) -> u32;

fn as_u64(&self) -> u64;

fn as_u128(&self) -> u128;

#[inline]
fn as_<T: Number>(self) -> T {
T::masked_new(self)
}
}

#[cfg(feature = "const_convert_and_const_trait_impl")]
Expand All @@ -82,6 +106,39 @@ macro_rules! impl_number_native {

#[inline]
fn value(self) -> Self::UnderlyingType { self }

#[inline]
fn new_<T: Number>(value: T) -> Self {
match Self::BITS {
8 => value.as_u8() as Self,

Check failure on line 113 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build-and-test

the trait bound `T: ~const Number` is not satisfied
16 => value.as_u16() as Self,

Check failure on line 114 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build-and-test

the trait bound `T: ~const Number` is not satisfied
32 => value.as_u32() as Self,

Check failure on line 115 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build-and-test

the trait bound `T: ~const Number` is not satisfied
64 => value.as_u64() as Self,

Check failure on line 116 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build-and-test

the trait bound `T: ~const Number` is not satisfied
128 => value.as_u128() as Self,

Check failure on line 117 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build-and-test

the trait bound `T: ~const Number` is not satisfied
_ => panic!("Unhandled Number type")
}
}

#[inline]
fn masked_new<T: Number>(value: T) -> Self {
// Primitive types don't need masking
Self::new_(value)
}

#[inline]
fn as_u8(&self) -> u8 { *self as u8 }

#[inline]
fn as_u16(&self) -> u16 { *self as u16 }

#[inline]
fn as_u32(&self) -> u32 { *self as u32 }

#[inline]
fn as_u64(&self) -> u64 { *self as u64 }

#[inline]
fn as_u128(&self) -> u128 { *self as u128 }
}
)+
};
Expand All @@ -105,6 +162,39 @@ macro_rules! impl_number_native {

#[inline]
fn value(self) -> Self::UnderlyingType { self }

#[inline]
fn new_<T: Number>(value: T) -> Self {
match Self::BITS {
8 => value.as_u8() as Self,
16 => value.as_u16() as Self,
32 => value.as_u32() as Self,
64 => value.as_u64() as Self,
128 => value.as_u128() as Self,
_ => panic!("Unhandled Number type")
}
}

#[inline]
fn masked_new<T: Number>(value: T) -> Self {
// Primitive types don't need masking
Self::new_(value)
}

#[inline]
fn as_u8(&self) -> u8 { *self as u8 }

#[inline]
fn as_u16(&self) -> u16 { *self as u16 }

#[inline]
fn as_u32(&self) -> u32 { *self as u32 }

#[inline]
fn as_u64(&self) -> u64 { *self as u64 }

#[inline]
fn as_u128(&self) -> u128 { *self as u128 }
}
)+
};
Expand Down Expand Up @@ -192,6 +282,31 @@ macro_rules! uint_impl_num {
fn value(self) -> $type {
self.value
}

#[inline]
fn as_u8(&self) -> u8 {
self.value as u8
}

#[inline]
fn as_u16(&self) -> u16 {
self.value as u16
}

#[inline]
fn as_u32(&self) -> u32 {
self.value as u32
}

#[inline]
fn as_u64(&self) -> u64 {
self.value as u64
}

#[inline]
fn as_u128(&self) -> u128 {
self.value as u128
}
}
)+
};
Expand Down Expand Up @@ -228,6 +343,39 @@ macro_rules! uint_impl_num {
Self { value }
}

#[inline]
fn new_<T: Number>(value: T) -> Self {
Self::new(Self::UnderlyingType::new_(value))
}

fn masked_new<T: Number>(value: T) -> Self {
if Self::BITS < T::BITS {
Self { value: Self::UnderlyingType::masked_new(value.as_::<Self::UnderlyingType>() & Self::MASK) }
} else {
Self { value: Self::UnderlyingType::masked_new(value) }
}
}

fn as_u8(&self) -> u8 {
self.value as _
}

fn as_u16(&self) -> u16 {
self.value as _
}

fn as_u32(&self) -> u32 {
self.value as _
}

fn as_u64(&self) -> u64 {
self.value as _
}

fn as_u128(&self) -> u128 {
self.value as _
}

#[inline]
fn value(self) -> $type {
self.value
Expand All @@ -251,6 +399,56 @@ macro_rules! uint_impl {
Self { value }
}

/// Creates an instance. Panics if the given value is outside of the valid range
#[inline]
pub const fn new_u8(value: u8) -> Self {
let value = value as $type;
if Self::BITS < 8 {
assert!(value <= Self::MAX.value);
}
Self { value }
}

/// Creates an instance. Panics if the given value is outside of the valid range
#[inline]
pub const fn new_u16(value: u16) -> Self {
let value = value as $type;
if Self::BITS < 16 {
assert!(value <= Self::MAX.value);
}
Self { value }
}

/// Creates an instance. Panics if the given value is outside of the valid range
#[inline]
pub const fn new_u32(value: u32) -> Self {
let value = value as $type;
if Self::BITS < 32 {
assert!(value <= Self::MAX.value);
}
Self { value }
}

/// Creates an instance. Panics if the given value is outside of the valid range
#[inline]
pub const fn new_u64(value: u64) -> Self {
let value = value as $type;
if Self::BITS < 64 {
assert!(value <= Self::MAX.value);
}
Self { value }
}

/// Creates an instance. Panics if the given value is outside of the valid range
#[inline]
pub const fn new_u128(value: u128) -> Self {
let value = value as $type;
if Self::BITS < 128 {
assert!(value <= Self::MAX.value);
}
Self { value }
}

/// Creates an instance or an error if the given value is outside of the valid range
#[inline]
pub const fn try_new(value: $type) -> Result<Self, TryNewError> {
Expand Down
50 changes: 50 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2047,3 +2047,53 @@ fn schemars() {
u8.schema.number = u9.schema.number.clone();
assert_eq!(u8, u9);
}

#[test]
fn new_and_as_specific_types() {
let a = u6::new(42);
let b = u6::new_u8(42);
let c = u6::new_u16(42);
let d = u6::new_u32(42);
let e = u6::new_u64(42);
let f = u6::new_u128(42);

assert_eq!(a.as_u8(), 42);
assert_eq!(a.as_u16(), 42);
assert_eq!(a.as_u32(), 42);
assert_eq!(a.as_u64(), 42);
assert_eq!(a.as_u128(), 42);
assert_eq!(b.as_u128(), 42);
assert_eq!(c.as_u128(), 42);
assert_eq!(d.as_u128(), 42);
assert_eq!(e.as_u128(), 42);
assert_eq!(f.as_u128(), 42);
}

#[test]
fn new_flexible() {
let a = u10::new(1000);
let b = u11::new_(a);

assert_eq!(a.as_u32(), 1000);
assert_eq!(b.as_u32(), 1000);
}

#[test]
#[should_panic]
fn new_flexible_catches_out_of_bounds() {
let a = u10::new(1000);
let _b = u9::new_(a);
}

#[test]
fn new_masked() {
let a = u16::new(1000);
let b = u9::masked_new(a);
assert_eq!(b.as_u32(), 488);
}

#[test]
fn as_flexible() {
let a: u32 = u14::new(123).as_();
assert_eq!(a, 123u32);
}

0 comments on commit 35b1079

Please sign in to comment.