diff --git a/README.md b/README.md index a74df35..609aafb 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,16 @@ # array-concat -Macros for concatenating arrays. +Macros for concatenating and splitting arrays. To add to your Cargo.toml: + ```toml array-concat = "0.5.3" ``` ## Example + ```rust use array_concat::*; @@ -31,6 +33,9 @@ fn main() { assert_eq!([1, 2, 3, 4, 5], c); assert_eq!([S(true), S(false)], F); + // Split the array into three parts with lengths: 1, 3, and 1 + assert_eq!(([1], [2, 3, 4], [5]), split_array!(c, 1, 3, 1)); + let a = [1, 2, 3]; let b = [4, 5]; let c = concat_arrays!(a, b); // Size is inferred by the assert below diff --git a/src/lib.rs b/src/lib.rs index 69a3c77..6c2051d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,8 +39,6 @@ macro_rules! concat_arrays { } impl ArrayConcatComposed { - const HAVE_SAME_SIZE: bool = core::mem::size_of::<[T; N]>() == core::mem::size_of::(); - const PANIC: bool = $crate::_const_assert_same_size::<[T; N], Self>(); #[inline(always)] @@ -63,22 +61,134 @@ macro_rules! concat_arrays { }); } +/// Flatten a nested tuple based on the number of nestings. +/// +/// This is an implementation detail of the crate and should only be used by the +/// macros in this crate. +#[macro_export] +#[doc(hidden)] +macro_rules! flatten_split { + (($($tail:tt)*), $head:expr, $pop:expr) => { + ($head, $($tail)*) + }; + // We can dramatically reduce macro recursion by adding an additional_case + (($($tail:tt)*), $head:expr, $pop1:expr, $pop2:expr$(, $remaining:expr)+) => { + $crate::flatten_split!( + ($head.1.2, $head.2, $($tail)*), + $head.1.1$(, + $remaining)+ + ) + }; + (($($tail:tt)*), $head:expr, $pop:expr$(, $remaining:expr)+) => { + $crate::flatten_split!( + ($head.2, $($tail)*), + $head.1$(, + $remaining)+ + ) + }; +} + +/// Split the provided array into the specified sizes. +#[macro_export] +macro_rules! split_array { + ($array:expr, $size:expr) => ($array); + ($array:expr, $size0:expr, $($sizes:expr),+) => ({ + struct ArrayConcatDecomposedMarkerBase(core::marker::PhantomData<(T, A)>); + struct ArrayConcatDecomposedMarker(core::marker::PhantomData<(T, A, B)>); + + #[repr(C)] + struct ArrayConcatDecomposed([T; 0], A, B); + + trait Storage { + type Data; + } + impl Storage for [T; A] { + type Data = [T; A]; + } + + impl ArrayConcatDecomposedMarkerBase { + #[inline(always)] + const fn default(_: &[T]) -> Self { + Self(core::marker::PhantomData) + } + #[inline(always)] + const fn concat(self, _: [(); N]) -> ArrayConcatDecomposedMarkerBase { + ArrayConcatDecomposedMarkerBase(core::marker::PhantomData) + } + } + impl ArrayConcatDecomposedMarkerBase { + #[inline(always)] + const fn concat(self, _: [(); B]) -> ArrayConcatDecomposedMarker { + ArrayConcatDecomposedMarker(core::marker::PhantomData) + } + } + + impl Storage for ArrayConcatDecomposedMarker { + type Data = ArrayConcatDecomposed; + } + + impl ArrayConcatDecomposedMarker { + #[inline(always)] + const fn concat(self, _: [(); C]) -> ArrayConcatDecomposedMarker, [T; C]> { + ArrayConcatDecomposedMarker(core::marker::PhantomData) + } + #[inline(always)] + const fn make(self, full: [T; N]) -> ArrayConcatDecomposed { + #[repr(C)] + union ArrayConcatComposed { + full: core::mem::ManuallyDrop<[T; N]>, + decomposed: core::mem::ManuallyDrop>, + } + + impl ArrayConcatComposed { + const PANIC: bool = $crate::_const_assert_same_size::<[T; N], Self>(); + + #[inline(always)] + const fn have_same_size(&self) -> bool { + Self::PANIC + } + } + + let composed = ArrayConcatComposed:: { + full: core::mem::ManuallyDrop::new(full) + }; + + // Sanity check that composed's two fields are the same size + composed.have_same_size(); + + // SAFETY: Sizes of both fields in composed are the same so this assignment should be sound + core::mem::ManuallyDrop::into_inner(unsafe { composed.decomposed }) + } + } + + + let array = $array; + let decomposed = ArrayConcatDecomposedMarkerBase::default(&array) + .concat([(); $size0]) + $(.concat([(); $sizes])) + *.make(array); + + $crate::flatten_split!((), decomposed, $size0$(, $sizes)*) + }); +} + /// Assert at compile time that these types have the same size. /// /// This is an implementation detail of the crate and should only be used by the /// macros in this crate. +#[inline(always)] #[doc(hidden)] pub const fn _const_assert_same_size() -> bool { let have_same_size = core::mem::size_of::() == core::mem::size_of::(); #[cfg(feature = "const_panic")] { - return have_same_size || panic!("Size Mismatch"); + have_same_size || panic!("Size Mismatch") } #[cfg(not(feature = "const_panic"))] { - return !["Size mismatch"][!have_same_size as usize].is_empty(); + !["Size mismatch"][!have_same_size as usize].is_empty() } } @@ -99,6 +209,25 @@ mod tests { assert_eq!([1, 2, 3, 4, 5, 6], d); } + #[test] + fn test_simple_split() { + let d: [u32; 6] = concat_arrays!(A, B); + const D: [u32; 6] = concat_arrays!(A, B); + + const A_B: ([u32; 3], [u32; 3]) = split_array!(D, A.len(), B.len()); + + assert_eq!((A, B), A_B); + assert_eq!((A, B), split_array!(d, 3, 3)); + assert_eq!(([1], [2, 3, 4, 5, 6]), split_array!(d, 1, 5)); + assert_eq!(([1, 2, 3, 4, 5], [6]), split_array!(d, 5, 1)); + assert_eq!(([1], [2, 3, 4, 5], [6]), split_array!(d, 1, 4, 1)); + assert_eq!(([1], [2, 3], [4, 5, 6]), split_array!(d, 1, 2, 3)); + assert_eq!( + ([1], [2], [3], [4], [5], [6]), + split_array!(d, 1, 1, 1, 1, 1, 1) + ); + } + #[test] fn test_different_sizes() { let e = concat_arrays!(A, C);