diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad2ccbd..c2a6727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,9 +12,9 @@ jobs: strategy: matrix: rust: [ - 1.91.0, # MSRV - stable, - beta, + # 1.91.0, # MSRV + # stable, + # beta, nightly, ] steps: @@ -32,7 +32,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + # - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly with: # try a target that doesn't have std at all target: thumbv6m-none-eabi @@ -42,7 +43,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + # - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly with: components: clippy - run: cargo clippy --all-features diff --git a/src/bytes.rs b/src/bytes.rs deleted file mode 100644 index 6ece3f5..0000000 --- a/src/bytes.rs +++ /dev/null @@ -1,129 +0,0 @@ -use core::array::TryFromSliceError; - -trait Sealed {} - -/// Trait for arrays of bytes that may be used in numeric conversions. -/// -/// In particular, this is used as a bound for the associated type -/// [`PrimitiveNumber::Bytes`][crate::PrimitiveNumber::Bytes] for converting numbers to and from an -/// array of bytes in various endian orders. It is simply `[u8; size_of::()]` for every -/// primitive number type, but there's no way yet to write that directly in the trait. -/// -/// This trait is not exhaustive of everything byte arrays can do, but it's enough to be useful for -/// generically constructing bytes and dealing with them as slices. -/// -/// This trait is sealed with a private trait to prevent downstream implementations, so we may -/// continue to expand along with the standard library without worrying about breaking changes for -/// implementors. -/// -/// # Examples -/// -/// The supertraits of `PrimitiveBytes` can be used without importing this trait directly: -/// -/// ``` -/// use num_primitive::PrimitiveNumber; -/// -/// // Return a value with the most significant bit set -/// fn msb() -> T { -/// let mut bytes = T::Bytes::default(); // prelude `Default` -/// bytes[0] = 0x80; // operator `IndexMut` -/// T::from_be_bytes(bytes) -/// } -/// -/// assert_eq!(msb::(), i64::MIN); -/// assert_eq!(msb::(), 1u16 << 15); -/// assert!(msb::().total_cmp(&-0.0).is_eq()); -/// ``` -/// -/// However, this trait must be imported to use its own methods like [`repeat`][Self::repeat]: -/// -/// ``` -/// use num_primitive::{PrimitiveBytes, PrimitiveNumber}; -/// -/// // Return a value with all bits set -/// fn all_ones() -> T { -/// T::from_ne_bytes(T::Bytes::repeat(0xff)) -/// } -/// -/// assert_eq!(all_ones::(), -1); -/// assert_eq!(all_ones::(), usize::MAX); -/// assert!(all_ones::().is_nan()); -/// ``` -/// -/// In cases where the size is known, you can use that as a constraint and then work with byte -/// arrays directly, regardless of this trait. -/// -/// ``` -/// use num_primitive::PrimitiveNumber; -/// -/// fn rust>() -> T { -/// T::from_be_bytes(*b"Rust") -/// } -/// -/// assert_eq!(rust::(), 0x52_75_73_74_i32); -/// assert_eq!(rust::(), 0x52_75_73_74_u32); -/// assert_eq!(rust::(), 2.63551e11); -/// ``` -#[expect(private_bounds)] -pub trait PrimitiveBytes: - 'static - + Sealed - + core::borrow::Borrow<[u8]> - + core::borrow::BorrowMut<[u8]> - + core::cmp::Eq - + core::cmp::Ord - + core::cmp::PartialEq<[u8]> - + core::convert::AsRef<[u8]> - + core::convert::AsMut<[u8]> - + core::default::Default - + core::fmt::Debug - + core::hash::Hash - + core::marker::Copy - + core::marker::Send - + core::marker::Sync - + core::marker::Unpin - + core::ops::Index - + core::ops::IndexMut - + core::panic::RefUnwindSafe - + core::panic::UnwindSafe - + for<'a> core::cmp::PartialEq<&'a [u8]> - + for<'a> core::cmp::PartialEq<&'a mut [u8]> - + for<'a> core::convert::TryFrom<&'a [u8], Error = TryFromSliceError> - + for<'a> core::convert::TryFrom<&'a mut [u8], Error = TryFromSliceError> -{ - /// Creates an array of bytes where each byte is produced by calling `f` - /// with that element's index while walking forward through the array. - /// - /// See the [`core::array::from_fn`] function. - fn from_fn(f: F) -> Self - where - F: FnMut(usize) -> u8; - - /// Creates an array of bytes with a repeat expression, `[value; N]`. - fn repeat(value: u8) -> Self; -} - -macro_rules! impl_bytes { - ($($N:literal),+) => {$( - impl Sealed for [u8; $N] {} - - impl PrimitiveBytes for [u8; $N] { - #[inline] - fn from_fn(f: F) -> Self - where - F: FnMut(usize) -> u8 - { - core::array::from_fn(f) - } - - #[inline] - fn repeat(value: u8) -> Self { - // We don't need to forward to `array::repeat` for cloning, - // since we can construct it directly with `u8` copies. - [value; $N] - } - } - )+} -} - -impl_bytes!(1, 2, 4, 8, 16); diff --git a/src/lib.rs b/src/lib.rs index 2901999..c95a328 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,9 @@ #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] +#![feature(min_generic_const_args)] +#![cfg_attr(test, feature(associated_const_equality))] +#![expect(incomplete_features)] #[cfg(feature = "std")] extern crate std; @@ -59,7 +62,6 @@ extern crate std; #[macro_use] mod macros; -mod bytes; mod error; mod float; mod integer; @@ -70,7 +72,6 @@ mod unsigned; #[cfg(test)] mod tests; -pub use self::bytes::PrimitiveBytes; pub use self::error::PrimitiveError; pub use self::float::{PrimitiveFloat, PrimitiveFloatRef, PrimitiveFloatToInt}; pub use self::integer::{PrimitiveInteger, PrimitiveIntegerRef}; diff --git a/src/number.rs b/src/number.rs index 84b5a5f..fe885f4 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,4 +1,4 @@ -use crate::{PrimitiveBytes, PrimitiveError}; +use crate::PrimitiveError; trait Sealed {} struct SealedToken; @@ -100,30 +100,31 @@ pub trait PrimitiveNumber: + for<'a> core::ops::Sub<&'a Self, Output = Self> + for<'a> core::ops::SubAssign<&'a Self> { - /// An array of bytes used by methods like [`from_be_bytes`][Self::from_be_bytes] and - /// [`to_be_bytes`][Self::to_be_bytes]. It is effectively `[u8; size_of::()]`. - type Bytes: PrimitiveBytes; + /// The number of bytes used by methods like [`from_be_bytes`][Self::from_be_bytes] and + /// [`to_be_bytes`][Self::to_be_bytes]. It is effectively `size_of::()`. + #[type_const] + const BYTES: usize; /// Creates a number from its representation as a byte array in big endian. - fn from_be_bytes(bytes: Self::Bytes) -> Self; + fn from_be_bytes(bytes: [u8; Self::BYTES]) -> Self; /// Creates a number from its representation as a byte array in little endian. - fn from_le_bytes(bytes: Self::Bytes) -> Self; + fn from_le_bytes(bytes: [u8; Self::BYTES]) -> Self; /// Creates a number from its representation as a byte array in native endian. - fn from_ne_bytes(bytes: Self::Bytes) -> Self; + fn from_ne_bytes(bytes: [u8; Self::BYTES]) -> Self; /// Calculates the midpoint (average) between `self` and `other`. fn midpoint(self, other: Self) -> Self; /// Returns the memory representation of this number as a byte array in little-endian order. - fn to_be_bytes(self) -> Self::Bytes; + fn to_be_bytes(self) -> [u8; Self::BYTES]; /// Returns the memory representation of this number as a byte array in big-endian order. - fn to_le_bytes(self) -> Self::Bytes; + fn to_le_bytes(self) -> [u8; Self::BYTES]; /// Returns the memory representation of this number as a byte array in native-endian order. - fn to_ne_bytes(self) -> Self::Bytes; + fn to_ne_bytes(self) -> [u8; Self::BYTES]; /// Creates a number using a type cast, `value as Self`. /// @@ -251,18 +252,19 @@ macro_rules! impl_primitive { impl Sealed for &$Number {} impl PrimitiveNumber for $Number { - type Bytes = [u8; size_of::()]; + #[type_const] + const BYTES: usize = const { size_of::() }; forward! { - fn from_be_bytes(bytes: Self::Bytes) -> Self; - fn from_le_bytes(bytes: Self::Bytes) -> Self; - fn from_ne_bytes(bytes: Self::Bytes) -> Self; + fn from_be_bytes(bytes: [u8; Self::BYTES]) -> Self; + fn from_le_bytes(bytes: [u8; Self::BYTES]) -> Self; + fn from_ne_bytes(bytes: [u8; Self::BYTES]) -> Self; } forward! { fn midpoint(self, other: Self) -> Self; - fn to_be_bytes(self) -> Self::Bytes; - fn to_le_bytes(self) -> Self::Bytes; - fn to_ne_bytes(self) -> Self::Bytes; + fn to_be_bytes(self) -> [u8; Self::BYTES]; + fn to_le_bytes(self) -> [u8; Self::BYTES]; + fn to_ne_bytes(self) -> [u8; Self::BYTES]; } } diff --git a/src/tests.rs b/src/tests.rs index 6b5d08f..f11848c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -58,3 +58,43 @@ fn try_into() { } check(0i32, 0u32); } + +// The following tests use `#![feature(min_generic_const_args)]` + +#[test] +fn bytes1() { + // Return a value with the most significant bit set + fn msb() -> T { + let mut bytes = [0; T::BYTES]; + bytes[0] = 0x80; + T::from_be_bytes(bytes) + } + + assert_eq!(msb::(), i64::MIN); + assert_eq!(msb::(), 1u16 << 15); + assert!(msb::().total_cmp(&-0.0).is_eq()); +} + +#[test] +fn bytes2() { + // Return a value with all bits set + fn all_ones() -> T { + T::from_ne_bytes(core::array::repeat(0xff)) + } + + assert_eq!(all_ones::(), -1); + assert_eq!(all_ones::(), usize::MAX); + assert!(all_ones::().is_nan()); +} + +#[test] +fn bytes3() { + // This also needs `#![feature(associated_const_equality)]` + fn rust>() -> T { + T::from_be_bytes(*b"Rust") + } + + assert_eq!(rust::(), 0x52_75_73_74_i32); + assert_eq!(rust::(), 0x52_75_73_74_u32); + assert_eq!(rust::(), 2.63551e11); +}