diff --git a/CHANGELOG.md b/CHANGELOG.md index 269c819..747ed80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,19 @@ # Changelog -## arbitrary-int 1.2.8 +## arbitrary-int 1.3.0 ### Added - New optional feature `hint`, which tells the compiler that the returned `value()` can't exceed a maximum value. This allows the compiler to optimize faster code at the expense of unsafe code within arbitrary-int itself. +- Various new const constructors: `new_u8`, `new_u16`, ..., `new_u128` which allow creating an arbitrary int without + type conversion, e.g. `u5::new_u32(i)` (where i is e.g. u32). This is shorter than writing + `u5::new(i.try_into().unwrap())`, + and combines two possible panic paths into one. Also, unlike `try_into().unwrap()`, the new constructors are usable in + const contexts. +- For non-const contexts, `new_()` allows any Number argument to be passed through generics. +- `as_()` easily converts any Number to another. `as_u8()`, `as_u16()` for more control (and to implement the others). +- New optional feature `borsh` to support binary serialization using the borsh crate. ## arbitrary-int 1.2.7 diff --git a/Cargo.toml b/Cargo.toml index 15638f4..0370ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "arbitrary-int" -version = "1.2.7" +version = "1.3.0" edition = "2021" authors = ["Daniel Lehmann "] description = "Modern and lightweight implementation of u2, u3, u4, ..., u127." @@ -15,7 +15,9 @@ std = [] # Supports const trait implementation through const_convert and const_trait_impl. Support for those was removed # from more recent Rust nightlies, so this feature requires an older Rust compiler -# (2023-04-20 is broken, 2022-11-23 works. The exact day is somewhere inbetween) +# (2023-04-20 is broken, 2022-11-23 works. The exact day is somewhere inbetween). +# As of 12/2/2024, this also uses inline_const. This has been stabilized but is required for current code to work +# on the old compiler. const_convert_and_const_trait_impl = [] # core::fmt::Step is currently unstable and is available on nightly behind a feature gate diff --git a/src/lib.rs b/src/lib.rs index fe75280..8696c65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr( feature = "const_convert_and_const_trait_impl", - feature(const_convert, const_trait_impl) + feature(const_convert, const_trait_impl, inline_const) )] #![cfg_attr(feature = "step_trait", feature(step_trait))] @@ -40,8 +40,9 @@ impl Display for TryNewError { } #[cfg_attr(feature = "const_convert_and_const_trait_impl", const_trait)] -pub trait Number: Sized { - type UnderlyingType: Debug +pub trait Number: Sized + Copy + Clone + PartialOrd + Ord + PartialEq + Eq { + type UnderlyingType: Number + + Debug + From + TryFrom + TryFrom @@ -57,11 +58,42 @@ 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. + /// This constructor is useful when creating a value from a literal. 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; fn value(self) -> Self::UnderlyingType; + + /// Creates a number from the given value, throwing an error if the value is too large. + /// This constructor is useful when the value is convertable to T. Use [`Self::new`] for literals. + #[cfg(not(feature = "const_convert_and_const_trait_impl"))] + fn from_(value: T) -> Self; + + /// Creates an instance from the given `value`. Unlike the various `new...` functions, this + /// will never fail as the value is masked to the result size. + #[cfg(not(feature = "const_convert_and_const_trait_impl"))] + fn masked_new(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; + + fn as_usize(&self) -> usize; + + #[cfg(not(feature = "const_convert_and_const_trait_impl"))] + #[inline] + fn as_(self) -> T { + T::masked_new(self) + } } #[cfg(feature = "const_convert_and_const_trait_impl")] @@ -82,6 +114,24 @@ macro_rules! impl_number_native { #[inline] fn value(self) -> Self::UnderlyingType { self } + + #[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 } + + #[inline] + fn as_usize(&self) -> usize { *self as usize } } )+ }; @@ -105,6 +155,45 @@ macro_rules! impl_number_native { #[inline] fn value(self) -> Self::UnderlyingType { self } + + #[inline] + fn from_(value: T) -> Self { + if T::BITS > Self::BITS as usize { + assert!(value <= T::masked_new(Self::MAX)); + } + Self::masked_new(value) + } + + #[inline] + fn masked_new(value: T) -> Self { + // Primitive types don't need masking + 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 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 } + + #[inline] + fn as_usize(&self) -> usize { *self as usize } } )+ }; @@ -112,14 +201,6 @@ macro_rules! impl_number_native { impl_number_native!(u8, u16, u32, u64, u128); -struct CompileTimeAssert {} - -impl CompileTimeAssert { - pub const SMALLER_OR_EQUAL: () = { - assert!(A <= B); - }; -} - #[derive(Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)] pub struct UInt { value: T, @@ -198,6 +279,36 @@ macro_rules! uint_impl_num { 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 + } + + #[inline] + fn as_usize(&self) -> usize { + self.value() as usize + } } )+ }; @@ -234,6 +345,46 @@ macro_rules! uint_impl_num { Self { value } } + #[inline] + fn from_(value: T) -> Self { + if Self::BITS < T::BITS { + assert!(value <= Self::MAX.value.as_()); + } + Self { value: Self::UnderlyingType::masked_new(value) } + } + + fn masked_new(value: T) -> Self { + if Self::BITS < T::BITS { + Self { value: Self::UnderlyingType::masked_new(value.as_::() & 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 _ + } + + fn as_usize(&self) -> usize { + self.value() as _ + } + #[inline] fn value(self) -> $type { #[cfg(feature = "hint")] @@ -262,6 +413,51 @@ macro_rules! uint_impl { Self { value } } + /// Creates an instance. Panics if the given value is outside of the valid range + #[inline] + pub const fn from_u8(value: u8) -> Self { + if Self::BITS < 8 { + assert!(value <= Self::MAX.value as u8); + } + Self { value: value as $type } + } + + /// Creates an instance. Panics if the given value is outside of the valid range + #[inline] + pub const fn from_u16(value: u16) -> Self { + if Self::BITS < 16 { + assert!(value <= Self::MAX.value as u16); + } + Self { value: value as $type } + } + + /// Creates an instance. Panics if the given value is outside of the valid range + #[inline] + pub const fn from_u32(value: u32) -> Self { + if Self::BITS < 32 { + assert!(value <= Self::MAX.value as u32); + } + Self { value: value as $type } + } + + /// Creates an instance. Panics if the given value is outside of the valid range + #[inline] + pub const fn from_u64(value: u64) -> Self { + if Self::BITS < 64 { + assert!(value <= Self::MAX.value as u64); + } + Self { value: value as $type } + } + + /// Creates an instance. Panics if the given value is outside of the valid range + #[inline] + pub const fn from_u128(value: u128) -> Self { + if Self::BITS < 128 { + assert!(value <= Self::MAX.value as u128); + } + Self { value: value as $type } + } + /// 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 { @@ -372,7 +568,10 @@ macro_rules! uint_impl { pub const fn widen( self, ) -> UInt<$type, BITS_RESULT> { - let _ = CompileTimeAssert::::SMALLER_OR_EQUAL; + const { if BITS >= BITS_RESULT { + panic!("Can not call widen() with the given bit widths"); + } }; + // Query MAX of the result to ensure we get a compiler error if the current definition is bogus (e.g. ) let _ = UInt::<$type, BITS_RESULT>::MAX; UInt::<$type, BITS_RESULT> { value: self.value } @@ -1468,7 +1667,10 @@ macro_rules! from_arbitrary_int_impl { { #[inline] fn from(item: UInt<$from, BITS_FROM>) -> Self { - let _ = CompileTimeAssert::::SMALLER_OR_EQUAL; + const { if BITS_FROM > BITS { + panic!("Can not call from() to convert between the given bit widths."); + } }; + Self { value: item.value as $into } } } @@ -1485,7 +1687,10 @@ macro_rules! from_arbitrary_int_impl { { #[inline] fn from(item: UInt<$from, BITS_FROM>) -> Self { - let _ = CompileTimeAssert::::SMALLER_OR_EQUAL; + const { if BITS_FROM > BITS { + panic!("Can not call from() to convert between the given bit widths."); + } }; + Self { value: item.value as $into } } } @@ -1500,7 +1705,9 @@ macro_rules! from_native_impl { impl const From<$from> for UInt<$into, BITS> { #[inline] fn from(from: $from) -> Self { - let _ = CompileTimeAssert::<{ <$from>::BITS as usize }, BITS>::SMALLER_OR_EQUAL; + const { if <$from>::BITS as usize > BITS { + panic!("Can not call from() to convert between the given bit widths."); + } }; Self { value: from as $into } } } @@ -1508,7 +1715,9 @@ macro_rules! from_native_impl { impl const From> for $into { #[inline] fn from(from: UInt<$from, BITS>) -> Self { - let _ = CompileTimeAssert::::BITS as usize }>::SMALLER_OR_EQUAL; + const { if BITS > <$from>::BITS as usize { + panic!("Can not call from() to convert between the given bit widths."); + } }; from.value as $into } } @@ -1523,7 +1732,9 @@ macro_rules! from_native_impl { impl From<$from> for UInt<$into, BITS> { #[inline] fn from(from: $from) -> Self { - let _ = CompileTimeAssert::<{ <$from>::BITS as usize }, BITS>::SMALLER_OR_EQUAL; + const { if <$from>::BITS as usize > BITS { + panic!("Can not call from() to convert between the given bit widths."); + } }; Self { value: from as $into } } } @@ -1531,7 +1742,9 @@ macro_rules! from_native_impl { impl From> for $into { #[inline] fn from(from: UInt<$from, BITS>) -> Self { - let _ = CompileTimeAssert::::BITS as usize }>::SMALLER_OR_EQUAL; + const { if BITS > <$from>::BITS as usize { + panic!("Can not call from() to convert between the given bit widths."); + } }; from.value as $into } } @@ -1610,7 +1823,7 @@ macro_rules! boolu1 { match value.value() { 0 => false, 1 => true, - _ => panic!("arbitrary_int_type already validates that this is unreachable"), //TODO: unreachable!() is not const yet + _ => unreachable!(), } } } diff --git a/tests/tests.rs b/tests/tests.rs index 245e135..708040e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2071,3 +2071,111 @@ 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::from_u8(42); + let c = u6::from_u16(42); + let d = u6::from_u32(42); + let e = u6::from_u64(42); + let f = u6::from_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); + assert_eq!(f.as_usize(), 42); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +fn from_flexible() { + let a = u10::new(1000); + let b = u11::from_(a); + + assert_eq!(a.as_u32(), 1000); + assert_eq!(b.as_u32(), 1000); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn from_flexible_catches_out_of_bounds() { + let a = u28::new(0x8000000); + let _b = u9::from_(a); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn from_flexible_catches_out_of_bounds_2() { + let a = u28::new(0x0000200); + let _b = u9::from_(a); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn from_flexible_catches_out_of_bounds_primitive_type() { + let a = u28::new(0x8000000); + let _b = u8::from_(a); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn new_constructors_catch_out_bounds_0() { + u7::from_u8(0x80u8); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn new_constructors_catch_out_bounds_1() { + u7::from_u32(0x80000060u32); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn new_constructors_catch_out_bounds_2() { + u7::from_u16(0x8060u16); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn new_constructors_catch_out_bounds_3() { + u7::from_u64(0x80000000_00000060u64); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +#[should_panic] +fn new_constructors_catch_out_bounds_4() { + u7::from_u128(0x80000000_00000000_00000000_00000060u128); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +fn new_masked() { + let a = u10::new(0b11_01010111); + let b = u9::masked_new(a); + assert_eq!(b.as_u32(), 0b1_01010111); + let c = u7::masked_new(a); + assert_eq!(c.as_u32(), 0b1010111); +} + +#[cfg(not(feature = "const_convert_and_const_trait_impl"))] +#[test] +fn as_flexible() { + let a: u32 = u14::new(123).as_(); + assert_eq!(a, 123u32); +}