Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modify ArraySize to allow for arbitrary size support by third parties #81

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/from_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ where
///
/// Propagates the `E` type returned from the provided `F` in the event of error.
pub fn try_from_fn<E>(f: impl FnMut(usize) -> Result<T, E>) -> Result<Self, E> {
core::convert::identity(U::__CHECK_INVARIANT);
let mut array = Array::<MaybeUninit<T>, U>::uninit();
try_from_fn_erased(array.0.as_mut(), f)?;

Expand Down
43 changes: 29 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -207,7 +207,7 @@ where
U: Add<N>,
Sum<U, N>: ArraySize,
{
self.into_iter().chain(other.into_iter()).collect()
self.into_iter().chain(other).collect()
}

/// Splits `self` at index `N` in two arrays.
Expand All @@ -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(<N::Size as Unsigned>::USIZE).cast());
(head, tail)
}
}
Expand All @@ -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(<N::Size as Unsigned>::USIZE).cast();
(head, tail)
}
}
Expand All @@ -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(<N::Size as Unsigned>::USIZE).cast();
(head, tail)
}
}
Expand All @@ -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!(
<U::Size as Unsigned>::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() / <U::Size as Unsigned>::USIZE;
let tail_pos = <U::Size as Unsigned>::USIZE * chunks_len;
let tail_len = buf.len() - tail_pos;
unsafe {
let ptr = buf.as_ptr();
Expand All @@ -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!(
<U::Size as Unsigned>::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() / <U::Size as Unsigned>::USIZE;
let tail_pos = <U::Size as Unsigned>::USIZE * chunks_len;
let tail_len = buf.len() - tail_pos;
unsafe {
let ptr = buf.as_mut_ptr();
Expand Down Expand Up @@ -605,6 +613,7 @@ where
{
#[inline]
fn from(arr: [T; N]) -> Array<T, U> {
core::convert::identity(U::__CHECK_INVARIANT);
Array(arr)
}
}
Expand Down Expand Up @@ -766,7 +775,8 @@ where

#[inline]
fn try_from(slice: &'a [T]) -> Result<Array<T, U>, TryFromSliceError> {
<&'a Self>::try_from(slice).map(Clone::clone)
core::convert::identity(U::__CHECK_INVARIANT);
<&'a Self>::try_from(slice).cloned()
}
}

Expand All @@ -778,6 +788,7 @@ where

#[inline]
fn try_from(slice: &'a [T]) -> Result<Self, TryFromSliceError> {
core::convert::identity(U::__CHECK_INVARIANT);
check_slice_length::<T, U>(slice)?;

// SAFETY: `Array<T, U>` is a `repr(transparent)` newtype for a core
Expand All @@ -794,6 +805,7 @@ where

#[inline]
fn try_from(slice: &'a mut [T]) -> Result<Self, TryFromSliceError> {
core::convert::identity(U::__CHECK_INVARIANT);
check_slice_length::<T, U>(slice)?;

// SAFETY: `Array<T, U>` is a `repr(transparent)` newtype for a core
Expand Down Expand Up @@ -825,9 +837,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<T, U: ArraySize>(slice: &[T]) -> Result<(), TryFromSliceError> {
debug_assert_eq!(Array::<(), U>::default().len(), U::USIZE);
debug_assert_eq!(
Array::<(), U>::default().len(),
<U::Size as Unsigned>::USIZE
);

if slice.len() != U::USIZE {
if slice.len() != <U::Size as Unsigned>::USIZE {
// Hack: `TryFromSliceError` lacks a public constructor
<&[T; 1]>::try_from([].as_slice())?;

Expand Down
1 change: 1 addition & 0 deletions src/sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ macro_rules! impl_array_sizes {
($($len:expr => $ty:ident),+ $(,)?) => {
$(
unsafe impl ArraySize for $ty {
type Size = $ty;
type ArrayType<T> = [T; $len];
}

Expand Down
23 changes: 15 additions & 8 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::Array;
use core::{
borrow::{Borrow, BorrowMut},
mem::size_of,
ops::{Index, IndexMut, Range},
};
use typenum::Unsigned;
Expand All @@ -13,17 +14,23 @@ 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 {
Comment on lines -16 to -20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use Unsigned::USIZE quite a bit, FWIW. I guess all of these usages would hypothetically have to change to ArraySize::Size::USIZE

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that goes for my follow up suggestion of adding the convenience const. Although you do run into ambiguities then

/// [`Size::USIZE`](Unsigned::USIZE). Breaking this requirement will cause undefined behavior.
pub unsafe trait ArraySize: Sized + 'static {
#[doc(hidden)]
const __CHECK_INVARIANT: () = {
let a = <Self::Size as Unsigned>::USIZE;
let b = size_of::<Self::ArrayType<u8>>();
assert!(a == b, "ArraySize invariant violated");
};

/// 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`].
type ArrayType<T>: AssocArraySize<Size = Self>
+ AsRef<[T]>
/// [`ArraySize::Size::USIZE`][`typenum::Unsigned::USIZE`].
type ArrayType<T>: AsRef<[T]>
+ AsMut<[T]>
+ Borrow<[T]>
+ BorrowMut<[T]>
Expand Down
23 changes: 23 additions & 0 deletions tests/custom_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use hybrid_array::Array;

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<T> = [T; 54321];
}

#[test]
fn from_fn() {
let array = Array::<u8, Size54321>::from_fn(|n| (n + 1) as u8);
assert_eq!(array.as_slice().len(), 54321);
}