From 78cda91e824453d9b959271b4e4cd301b88ab7de Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 21 Jul 2024 09:59:03 +0100 Subject: [PATCH 1/5] attempt a different design that allows for custom defined sizes --- src/lib.rs | 35 +++++++++++------ src/sizes.rs | 1 + src/traits.rs | 13 +++---- tests/custom_size.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 tests/custom_size.rs diff --git a/src/lib.rs b/src/lib.rs index 4c8e037..94e4b47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ use core::{ ptr, slice::{self, Iter, IterMut}, }; -use typenum::{Diff, Sum}; +use typenum::{Diff, Sum, Unsigned}; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -223,7 +223,7 @@ where unsafe { let array = ManuallyDrop::new(self); let head = ptr::read(array.as_ptr().cast()); - let tail = ptr::read(array.as_ptr().add(N::USIZE).cast()); + let tail = ptr::read(array.as_ptr().add(::USIZE).cast()); (head, tail) } } @@ -239,7 +239,7 @@ where unsafe { let array_ptr = self.as_ptr(); let head = &*array_ptr.cast(); - let tail = &*array_ptr.add(N::USIZE).cast(); + let tail = &*array_ptr.add(::USIZE).cast(); (head, tail) } } @@ -255,7 +255,7 @@ where unsafe { let array_ptr = self.as_mut_ptr(); let head = &mut *array_ptr.cast(); - let tail = &mut *array_ptr.add(N::USIZE).cast(); + let tail = &mut *array_ptr.add(::USIZE).cast(); (head, tail) } } @@ -268,12 +268,16 @@ where #[allow(clippy::arithmetic_side_effects)] #[inline] pub fn slice_as_chunks(buf: &[T]) -> (&[Self], &[T]) { - assert_ne!(U::USIZE, 0, "chunk size must be non-zero"); + assert_ne!( + ::USIZE, + 0, + "chunk size must be non-zero" + ); // Arithmetic safety: we have checked that `N::USIZE` is not zero, thus // division always returns correct result. `tail_pos` can not be bigger than `buf.len()`, // thus overflow on multiplication and underflow on substraction are impossible. - let chunks_len = buf.len() / U::USIZE; - let tail_pos = U::USIZE * chunks_len; + let chunks_len = buf.len() / ::USIZE; + let tail_pos = ::USIZE * chunks_len; let tail_len = buf.len() - tail_pos; unsafe { let ptr = buf.as_ptr(); @@ -291,12 +295,16 @@ where #[allow(clippy::arithmetic_side_effects)] #[inline] pub fn slice_as_chunks_mut(buf: &mut [T]) -> (&mut [Self], &mut [T]) { - assert_ne!(U::USIZE, 0, "chunk size must be non-zero"); + assert_ne!( + ::USIZE, + 0, + "chunk size must be non-zero" + ); // Arithmetic safety: we have checked that `N::USIZE` is not zero, thus // division always returns correct result. `tail_pos` can not be bigger than `buf.len()`, // thus overflow on multiplication and underflow on substraction are impossible. - let chunks_len = buf.len() / U::USIZE; - let tail_pos = U::USIZE * chunks_len; + let chunks_len = buf.len() / ::USIZE; + let tail_pos = ::USIZE * chunks_len; let tail_len = buf.len() - tail_pos; unsafe { let ptr = buf.as_mut_ptr(); @@ -825,9 +833,12 @@ where /// Generate a [`TryFromSliceError`] if the slice doesn't match the given length. #[cfg_attr(debug_assertions, allow(clippy::panic_in_result_fn))] fn check_slice_length(slice: &[T]) -> Result<(), TryFromSliceError> { - debug_assert_eq!(Array::<(), U>::default().len(), U::USIZE); + debug_assert_eq!( + Array::<(), U>::default().len(), + ::USIZE + ); - if slice.len() != U::USIZE { + if slice.len() != ::USIZE { // Hack: `TryFromSliceError` lacks a public constructor <&[T; 1]>::try_from([].as_slice())?; diff --git a/src/sizes.rs b/src/sizes.rs index 28888eb..d5f1ec9 100644 --- a/src/sizes.rs +++ b/src/sizes.rs @@ -22,6 +22,7 @@ macro_rules! impl_array_sizes { ($($len:expr => $ty:ident),+ $(,)?) => { $( unsafe impl ArraySize for $ty { + type Size = $ty; type ArrayType = [T; $len]; } diff --git a/src/traits.rs b/src/traits.rs index d5824a9..1e6897d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -13,17 +13,16 @@ use typenum::Unsigned; /// # Safety /// /// `ArrayType` MUST be an array with a number of elements exactly equal to -/// [`Unsigned::USIZE`]. Breaking this requirement will cause undefined behavior. -/// -/// NOTE: This trait is effectively sealed and can not be implemented by third-party crates. -/// It is implemented only for a number of types defined in [`typenum::consts`]. -pub unsafe trait ArraySize: Unsigned { +/// [`Size::USIZE`]. Breaking this requirement will cause undefined behavior. +pub unsafe trait ArraySize: Sized + 'static { + /// The size underlying + type Size: Unsigned; + /// Array type which corresponds to this size. /// /// This is always defined to be `[T; N]` where `N` is the same as /// [`ArraySize::USIZE`][`typenum::Unsigned::USIZE`]. - type ArrayType: AssocArraySize - + AsRef<[T]> + type ArrayType: AsRef<[T]> + AsMut<[T]> + Borrow<[T]> + BorrowMut<[T]> diff --git a/tests/custom_size.rs b/tests/custom_size.rs new file mode 100644 index 0000000..d4f9b0a --- /dev/null +++ b/tests/custom_size.rs @@ -0,0 +1,93 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + ops::{Index, IndexMut, Range}, +}; + +use hybrid_array::Array; + +struct CustomArray([T; 54321]); +impl AsRef<[T]> for CustomArray { + fn as_ref(&self) -> &[T] { + &self.0 + } +} +impl AsMut<[T]> for CustomArray { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} +impl Borrow<[T]> for CustomArray { + fn borrow(&self) -> &[T] { + &self.0 + } +} +impl BorrowMut<[T]> for CustomArray { + fn borrow_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} +impl From> for CustomArray { + fn from(value: Array) -> Self { + value.0 + } +} +impl From> for Array { + fn from(val: CustomArray) -> Self { + Array(val) + } +} +impl IntoIterator for CustomArray { + type Item = T; + + type IntoIter = std::array::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} +impl Index for CustomArray { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} +impl IndexMut for CustomArray { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} +impl Index> for CustomArray { + type Output = [T]; + + fn index(&self, index: Range) -> &Self::Output { + &self.0[index] + } +} +impl IndexMut> for CustomArray { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + &mut self.0[index] + } +} + +struct Size54321; + +// This macro constructs a UInt type from a sequence of bits. The bits are interpreted as the +// little-endian representation of the integer in question. For example, uint!(1 1 0 1 0 0 1) is +// U75 (not U105). +macro_rules! uint { + () => { typenum::UTerm }; + (0 $($bs:tt)*) => { typenum::UInt< uint!($($bs)*), typenum::B0 > }; + (1 $($bs:tt)*) => { typenum::UInt< uint!($($bs)*), typenum::B1 > }; +} + +unsafe impl hybrid_array::ArraySize for Size54321 { + type Size = uint!(1 0 0 0 1 1 0 0 0 0 1 0 1 0 1 1); + type ArrayType = CustomArray; +} + +#[test] +fn from_fn() { + let array = Array::::from_fn(|n| (n + 1) as u8); + assert_eq!(array.as_slice().len(), 54321); +} From c30ca15ff0e130804a02b57ea618c62e47cde257 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 21 Jul 2024 10:00:03 +0100 Subject: [PATCH 2/5] remove custom array --- tests/custom_size.rs | 72 +------------------------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/tests/custom_size.rs b/tests/custom_size.rs index d4f9b0a..5c52cc7 100644 --- a/tests/custom_size.rs +++ b/tests/custom_size.rs @@ -1,75 +1,5 @@ -use std::{ - borrow::{Borrow, BorrowMut}, - ops::{Index, IndexMut, Range}, -}; - use hybrid_array::Array; -struct CustomArray([T; 54321]); -impl AsRef<[T]> for CustomArray { - fn as_ref(&self) -> &[T] { - &self.0 - } -} -impl AsMut<[T]> for CustomArray { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} -impl Borrow<[T]> for CustomArray { - fn borrow(&self) -> &[T] { - &self.0 - } -} -impl BorrowMut<[T]> for CustomArray { - fn borrow_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} -impl From> for CustomArray { - fn from(value: Array) -> Self { - value.0 - } -} -impl From> for Array { - fn from(val: CustomArray) -> Self { - Array(val) - } -} -impl IntoIterator for CustomArray { - type Item = T; - - type IntoIter = std::array::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} -impl Index for CustomArray { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - &self.0[index] - } -} -impl IndexMut for CustomArray { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.0[index] - } -} -impl Index> for CustomArray { - type Output = [T]; - - fn index(&self, index: Range) -> &Self::Output { - &self.0[index] - } -} -impl IndexMut> for CustomArray { - fn index_mut(&mut self, index: Range) -> &mut Self::Output { - &mut self.0[index] - } -} - struct Size54321; // This macro constructs a UInt type from a sequence of bits. The bits are interpreted as the @@ -83,7 +13,7 @@ macro_rules! uint { unsafe impl hybrid_array::ArraySize for Size54321 { type Size = uint!(1 0 0 0 1 1 0 0 0 0 1 0 1 0 1 1); - type ArrayType = CustomArray; + type ArrayType = [T; 54321]; } #[test] From 7866f3eafb3a08074ca91e9ec9fc79c0aea76fee Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 21 Jul 2024 10:08:22 +0100 Subject: [PATCH 3/5] fix comment --- src/traits.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 1e6897d..812f8ba 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -13,15 +13,15 @@ use typenum::Unsigned; /// # Safety /// /// `ArrayType` MUST be an array with a number of elements exactly equal to -/// [`Size::USIZE`]. Breaking this requirement will cause undefined behavior. +/// [`Self::Size::USIZE`]. Breaking this requirement will cause undefined behavior. pub unsafe trait ArraySize: Sized + 'static { - /// The size underlying + /// The size underlying the array. type Size: Unsigned; /// Array type which corresponds to this size. /// /// This is always defined to be `[T; N]` where `N` is the same as - /// [`ArraySize::USIZE`][`typenum::Unsigned::USIZE`]. + /// [`ArraySize::Size::USIZE`][`typenum::Unsigned::USIZE`]. type ArrayType: AsRef<[T]> + AsMut<[T]> + Borrow<[T]> From ba61d25982c67344079a7734c8b8846f722fbc80 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 21 Jul 2024 10:18:26 +0100 Subject: [PATCH 4/5] introduce invariant check to catch incorrect impls at compile time --- src/from_fn.rs | 1 + src/lib.rs | 4 ++++ src/traits.rs | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/src/from_fn.rs b/src/from_fn.rs index b10f87d..0edde10 100644 --- a/src/from_fn.rs +++ b/src/from_fn.rs @@ -24,6 +24,7 @@ where /// /// Propagates the `E` type returned from the provided `F` in the event of error. pub fn try_from_fn(f: impl FnMut(usize) -> Result) -> Result { + core::convert::identity(U::__CHECK_INVARIANT); let mut array = Array::, U>::uninit(); try_from_fn_erased(array.0.as_mut(), f)?; diff --git a/src/lib.rs b/src/lib.rs index 94e4b47..64555ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -613,6 +613,7 @@ where { #[inline] fn from(arr: [T; N]) -> Array { + core::convert::identity(U::__CHECK_INVARIANT); Array(arr) } } @@ -774,6 +775,7 @@ where #[inline] fn try_from(slice: &'a [T]) -> Result, TryFromSliceError> { + core::convert::identity(U::__CHECK_INVARIANT); <&'a Self>::try_from(slice).map(Clone::clone) } } @@ -786,6 +788,7 @@ where #[inline] fn try_from(slice: &'a [T]) -> Result { + core::convert::identity(U::__CHECK_INVARIANT); check_slice_length::(slice)?; // SAFETY: `Array` is a `repr(transparent)` newtype for a core @@ -802,6 +805,7 @@ where #[inline] fn try_from(slice: &'a mut [T]) -> Result { + core::convert::identity(U::__CHECK_INVARIANT); check_slice_length::(slice)?; // SAFETY: `Array` is a `repr(transparent)` newtype for a core diff --git a/src/traits.rs b/src/traits.rs index 812f8ba..a42c9cf 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -15,6 +15,13 @@ use typenum::Unsigned; /// `ArrayType` MUST be an array with a number of elements exactly equal to /// [`Self::Size::USIZE`]. Breaking this requirement will cause undefined behavior. pub unsafe trait ArraySize: Sized + 'static { + #[doc(hidden)] + const __CHECK_INVARIANT: () = { + let a = ::USIZE; + let b = core::mem::size_of::>(); + assert!(a == b, "ArraySize invariant violated"); + }; + /// The size underlying the array. type Size: Unsigned; From fd5394f23e41ae11b5514098726ab4809b6e803a Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 21 Jul 2024 10:28:27 +0100 Subject: [PATCH 5/5] fix lints --- src/lib.rs | 4 ++-- src/traits.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 64555ef..28c2212 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,7 +207,7 @@ where U: Add, Sum: ArraySize, { - self.into_iter().chain(other.into_iter()).collect() + self.into_iter().chain(other).collect() } /// Splits `self` at index `N` in two arrays. @@ -776,7 +776,7 @@ where #[inline] fn try_from(slice: &'a [T]) -> Result, TryFromSliceError> { core::convert::identity(U::__CHECK_INVARIANT); - <&'a Self>::try_from(slice).map(Clone::clone) + <&'a Self>::try_from(slice).cloned() } } diff --git a/src/traits.rs b/src/traits.rs index a42c9cf..91a50a9 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,6 +3,7 @@ use crate::Array; use core::{ borrow::{Borrow, BorrowMut}, + mem::size_of, ops::{Index, IndexMut, Range}, }; use typenum::Unsigned; @@ -13,12 +14,12 @@ use typenum::Unsigned; /// # Safety /// /// `ArrayType` MUST be an array with a number of elements exactly equal to -/// [`Self::Size::USIZE`]. Breaking this requirement will cause undefined behavior. +/// [`Size::USIZE`](Unsigned::USIZE). Breaking this requirement will cause undefined behavior. pub unsafe trait ArraySize: Sized + 'static { #[doc(hidden)] const __CHECK_INVARIANT: () = { let a = ::USIZE; - let b = core::mem::size_of::>(); + let b = size_of::>(); assert!(a == b, "ArraySize invariant violated"); };