diff --git a/Cargo.toml b/Cargo.toml index b101ff5..3bca800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "array_manipulation" description = "Methods for manipuling arrays in a Vec-like fashion. It will (probably) get into core once const expressions get less experimental." -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["Guillem Jara <4lon3ly0@tutanota.com>"] license = "MIT" diff --git a/README.md b/README.md index ec9f61e..27926b6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Manipulate Arrays as if they were vectors! -This crate exposes a trait that allows manipulating arrays in a vec-like fashion. +This crate exposes 2 traits that allow manipulating arrays in a vec-like fashion. Alternatives like [ArrayVec](https://docs.rs/arrayvec/latest/arrayvec/struct.ArrayVec.html) operate over a `[MaybeUninit; N]`-like data structure and panic if the size is overflown. diff --git a/src/lib.rs b/src/lib.rs index f705061..949914a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ #![feature(const_transmute_copy)] #![feature(const_ptr_read)] #![feature(specialization)] -#![no_std] +#![cfg_attr(not(test), no_std)] #![doc = include_str!("../README.md")] use core::{ @@ -20,56 +20,84 @@ use core::{ /// Will (probably) get into core when /// [generic-const-exprs](https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html) /// becomes complete. -pub trait ArrayAppend: Sized { +// TODO implement append_at & concat_at when const exprs become usable enough +pub trait ArrayAdd: Sized { + /// Inserts an element at the end of Self. Use concat for >1 elements. + /// In order to avoid unnecessary calls to `memcpy()`. + /// # Examples + /// ``` + /// use array_manipulation::ArrayAdd; + /// + /// let array: [u8; 4] = [1, 2, 3, 4]; + /// let expected = [1, 2, 3, 4, 5]; + /// let result = array.append(5); + /// assert_eq!(expected, result); + /// ``` + fn append(self, e: T) -> [T; N + 1]; + + /// Inserts an element at the start of Self. Use concat for >1 elements. + /// In order to avoid unnecessary calls to `memcpy()`. + /// # Examples + /// ``` + /// use array_manipulation::ArrayAdd; + /// + /// let array: [u8; 4] = [1, 2, 3, 4]; + /// let expected = [0, 1, 2, 3, 4]; + /// let result = array.append_back(0); + /// assert_eq!(expected, result); + /// ``` + fn append_back(self, e: T) -> [T; N + 1]; + /// Takes an array of L elements and appends it at the end of Self. /// Performs 2 calls to `memcpy()`, so if your code heavily uses it /// maybe a linked list is a better fit for your use case. /// # Examples /// ``` - /// use array_manipulation::ArrayManipulation; + /// use array_manipulation::ArrayAdd; /// /// let array: [u8; 4] = [1, 2, 3, 4]; /// let expected = [1, 2, 3, 4, 5, 6, 7]; - /// let result = array.push([5, 6, 7]); + /// let result = array.concat([5, 6, 7]); /// assert_eq!(expected, result); /// ``` - fn push(self, array: [T; L]) -> [T; N + L]; + fn concat(self, array: [T; L]) -> [T; N + L]; - /// Takes an array of L elements and appends it at the start of Self. + /// Takes an array of L elements and appends it at the end of Self. /// Performs 2 calls to `memcpy()`, so if your code heavily uses it /// maybe a linked list is a better fit for your use case. /// # Examples /// ``` - /// use array_manipulation::ArrayManipulation; + /// use array_manipulation::ArrayAdd; /// /// let array: [u8; 4] = [1, 2, 3, 4]; - /// let expected = [0, 1, 2, 3, 4]; - /// let result = array.push_back([0]); + /// let expected = [254, 255, 0, 1, 2, 3, 4]; + /// let result = array.concat_back([254, 255, 0]); /// assert_eq!(expected, result); /// ``` - fn push_back(self, array: [T; L]) -> [T; N + L]; + fn concat_back(self, array: [T; L]) -> [T; N + L]; } /// Holds the pop methods. /// Will (probably) get into core when /// [generic-const-exprs](https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html) /// becomes complete. -pub trait ArrayPop: Sized { - /// `memcpy()`s all the elements on an array except the last one. +// TODO implement pop_at when const exprs become usable enough +pub trait ArrayRemove: Sized { + /// `memcpy()`s all the elements on an array except the first L ones. /// Basically it creates a new fixed-size array with all the - /// elements except the last one. Won't compile if N == 0. + /// elements except the first one. Won't compile if N == 0. /// # Examples /// ``` /// use array_manipulation::ArrayManipulation; /// /// let array: [u8; 4] = [1, 2, 3, 4]; - /// let expected = [1, 2, 3]; - /// let result = array.pop(); + /// let expected = [3, 4]; + /// let result = array.truncate_start(2); /// assert_eq!(expected, result); /// ``` - fn pop(self) -> [T; N - 1]; + fn truncate_start(self) -> [T; N - L]; - /// `memcpy()`s all the elements on an array except the first one. + /// `memcpy()`s all the elements on an array except the last L ones. /// Basically it creates a new fixed-size array with all the /// elements except the first one. Won't compile if N == 0. /// # Examples @@ -77,15 +105,15 @@ pub trait ArrayPop: Sized { /// use array_manipulation::ArrayManipulation; /// /// let array: [u8; 4] = [1, 2, 3, 4]; - /// let expected = [2, 3, 4]; - /// let result = array.pop_back(); + /// let expected = [1, 2]; + /// let result = array.truncate_end(2); /// assert_eq!(expected, result); /// ``` - fn pop_back(self) -> [T; N - 1]; + fn truncate_end(self) -> [T; N - L]; } -impl const ArrayAppend for [T; N] { - default fn push(self, array: [T; L]) -> [T; N + L] { +impl const ArrayAdd for [T; N] { + default fn concat(self, array: [T; L]) -> [T; N + L] { let mut result: MaybeUninit<[T; N + L]> = MaybeUninit::uninit(); unsafe { copy_nonoverlapping(&raw const self, result.as_mut_ptr().cast(), 1); // copy elements @@ -103,7 +131,7 @@ impl const ArrayAppend for [T; N] { } } - default fn push_back(self, array: [T; L]) -> [T; N + L] { + default fn concat_back(self, array: [T; L]) -> [T; N + L] { let mut result: MaybeUninit<[T; N + L]> = MaybeUninit::uninit(); unsafe { copy_nonoverlapping(&raw const array, result.as_mut_ptr().cast(), 1); // copy elements @@ -120,40 +148,94 @@ impl const ArrayAppend for [T; N] { result.assume_init() // initialized } } + + default fn append(self, e: T) -> [T; N + 1] { + let mut result: MaybeUninit<[T; N + 1]> = MaybeUninit::uninit(); + unsafe { + copy_nonoverlapping(&raw const self, result.as_mut_ptr().cast(), 1); // copy elements + copy_nonoverlapping(&raw const e, result.as_mut_ptr().cast::().add(N), 1); // copy element + + // avoid drop & deallocation of the copied elements + forget(self); + forget(e); + + result.assume_init() + } + } + + default fn append_back(self, e: T) -> [T; N + 1] { + let mut result: MaybeUninit<[T; N + 1]> = MaybeUninit::uninit(); + unsafe { + copy_nonoverlapping(&raw const e, result.as_mut_ptr().cast::(), 1); // copy element + copy_nonoverlapping( + &raw const self, + result.as_mut_ptr().cast::().add(1).cast(), + 1, + ); // copy elements + + // avoid drop & deallocation of the copied elements + forget(self); + forget(e); + + result.assume_init() + } + } } -impl const ArrayAppend for [T; N] { - fn push(self, array: [T; L]) -> [T; N + L] { +impl const ArrayAdd for [T; N] { + fn concat(self, array: [T; L]) -> [T; N + L] { let mut result: MaybeUninit<[T; N + L]> = MaybeUninit::uninit(); unsafe { - *result.as_mut_ptr().cast() = self; - *result.as_mut_ptr().cast::().add(N).cast() = array; + *result.as_mut_ptr().cast() = self; // write + *result.as_mut_ptr().cast::().add(N).cast() = array; // offset ptr & write result.assume_init() // initialized } } - fn push_back(self, array: [T; L]) -> [T; N + L] { + fn concat_back(self, array: [T; L]) -> [T; N + L] { let mut result: MaybeUninit<[T; N + L]> = MaybeUninit::uninit(); unsafe { - *result.as_mut_ptr().cast() = array; // copy elements - *result.as_mut_ptr().cast::().add(L).cast() = self; + *result.as_mut_ptr().cast() = array; // write + *result.as_mut_ptr().cast::().add(L).cast() = self; // offset ptr & write result.assume_init() // initialized } } + + fn append(self, e: T) -> [T; N + 1] { + let mut result: MaybeUninit<[T; N + 1]> = MaybeUninit::uninit(); + unsafe { + copy_nonoverlapping(&raw const self, result.as_mut_ptr().cast(), 1); // copy elements + *result.as_mut_ptr().cast::().add(N) = e; // offset ptr & write + result.assume_init() + } + } + + fn append_back(self, e: T) -> [T; N + 1] { + let mut result: MaybeUninit<[T; N + 1]> = MaybeUninit::uninit(); + unsafe { + *result.as_mut_ptr().cast::() = e; // offset ptr & write + copy_nonoverlapping( + &raw const self, + result.as_mut_ptr().cast::().add(1).cast(), + 1, + ); // copy elements + result.assume_init() + } + } } -impl const ArrayPop for [T; N] { - default fn pop(self) -> [T; N - 1] { +impl const ArrayRemove for [T; N] { + default fn truncate_start(self) -> [T; N - L] { unsafe { - let result = transmute_copy(&self); // copy + let result = read((&raw const self).cast::().add(L).cast()); // copy from offset'ed pointer forget(self); // avoid drop & deallocation of the copied elements result } } - default fn pop_back(self) -> [T; N - 1] { + default fn truncate_end(self) -> [T; N - L] { unsafe { - let result = read((&raw const self).cast::().add(1).cast()); // copy from offset'ed pointer + let result = read((&raw const self).cast()); // copy from offset'ed pointer forget(self); // avoid drop & deallocation of the copied elements result } @@ -161,37 +243,122 @@ impl const ArrayPop for [T; N] { } #[allow(drop_bounds)] // specialization stuff -impl ArrayPop for [T; N] { - fn pop(mut self) -> [T; N - 1] { +impl ArrayRemove for [T; N] { + fn truncate_start(mut self) -> [T; N - L] { unsafe { - drop_in_place(&raw mut self[N - 1]); // drop popped element - let result = transmute_copy(&self); // copy elements + drop_in_place(&raw mut self[..L]); // drop popped element + let result = read((&raw const self).cast::().add(L).cast()); // copy from offset'ed pointer forget(self); // avoid drop & deallocation of the copied elements result } } - - fn pop_back(mut self) -> [T; N - 1] { + fn truncate_end(mut self) -> [T; N - L] { unsafe { - drop_in_place(&raw mut self[0]); // drop popped element - let result = read((&raw const self).cast::().add(1).cast()); // copy from offset'ed pointer + drop_in_place(&raw mut self[L..]); // drop popped element + let result = transmute_copy(&self); // copy elements forget(self); // avoid drop & deallocation of the copied elements result } } } -// Won't compile for some reason... hopefully specialization will get better soon -// impl const ArrayPop for [T; N] where Self: Copy { -// fn pop(self) -> [T; N - 1] { -// unsafe { -// *(&raw const self).cast() -// } -// } - -// fn pop_back(self) -> [T; N - 1] { -// unsafe { -// *(&raw const self).cast::().add(1).cast() -// } -// } -// } +#[cfg(test)] +mod tests { + use crate::{ArrayAdd, ArrayRemove}; + + #[test] + fn append_noncopy() { + let input = [vec![1, 2], vec![3, 4]]; + let expected = [vec![1, 2], vec![3, 4], vec![5, 6, 7]]; + let result = input.append(vec![5, 6, 7]); + assert_eq!(expected, result) + } + + #[test] + fn append_back_noncopy() { + let input = [vec![1, 2], vec![3, 4]]; + let expected = [vec![254, 255, 0], vec![1, 2], vec![3, 4]]; + let result = input.append_back(vec![254, 255, 0]); + assert_eq!(expected, result) + } + + #[test] + fn concat_noncopy() { + let input = [vec![1, 2], vec![3, 4]]; + let expected = [vec![1, 2], vec![3, 4], vec![5, 6, 7], vec![8, 9], vec![10, 11, 12]]; + let result = input.concat([vec![5, 6, 7], vec![8, 9], vec![10, 11, 12]]); + assert_eq!(expected, result) + } + + #[test] + fn concat_back_noncopy() { + let input = [vec![1, 2], vec![3, 4]]; + let expected = [vec![249, 250, 251], vec![252, 253], vec![254, 255, 0], vec![1, 2], vec![3, 4]]; + let result = input.concat_back([vec![249, 250, 251], vec![252, 253], vec![254, 255, 0]]); + assert_eq!(expected, result) + } + + #[test] + fn truncate_start_noncopy() { + let input = [vec![1, 2], vec![3, 4], vec![5, 6], vec![7, 8]]; + let expected = [vec![5, 6], vec![7, 8]]; + let result = input.truncate_start::<2>(); + assert_eq!(expected, result) + } + + #[test] + fn truncate_end_noncopy() { + let input = [vec![1, 2], vec![3, 4], vec![5, 6], vec![7, 8]]; + let expected = [vec![1, 2], vec![3, 4]]; + let result = input.truncate_end::<2>(); + assert_eq!(expected, result) + } + + #[test] + fn append_copy() { + let input = [1, 2, 3, 4]; + let expected = [1, 2, 3, 4, 5]; + let result = input.append(5); + assert_eq!(expected, result) + } + + #[test] + fn append_back_copy() { + let input = [1, 2, 3, 4]; + let expected = [0, 1, 2, 3, 4]; + let result = input.append_back(0); + assert_eq!(expected, result) + } + + #[test] + fn concat_copy() { + let input = [1, 2, 3, 4]; + let expected = [1, 2, 3, 4, 5, 6, 7]; + let result = input.concat([5, 6, 7]); + assert_eq!(expected, result) + } + + #[test] + fn concat_back_copy() { + let input = [1, 2, 3, 4]; + let expected = [254, 255, 0, 1, 2, 3, 4]; + let result = input.concat_back([254, 255, 0]); + assert_eq!(expected, result) + } + + #[test] + fn truncate_start_copy() { + let input = [1, 2, 3, 4]; + let expected = [3, 4]; + let result = input.truncate_start::<2>(); + assert_eq!(expected, result) + } + + #[test] + fn truncate_end_copy() { + let input = [1, 2, 3, 4]; + let expected = [1, 2]; + let result = input.truncate_end::<2>(); + assert_eq!(expected, result) + } +}