From 03ea0766ac6ea3ebcee126337cd81db1f206d845 Mon Sep 17 00:00:00 2001 From: Ole Magnus Fon Johnsen Date: Sun, 28 Sep 2025 10:17:14 +0200 Subject: [PATCH 1/3] ci: update rust toolchain configuration --- .github/workflows/ci.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 871f12f..d6f4e26 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - name: Install Rust toolchain + - name: Install Rust stable toolchain + uses: dtolnay/rust-toolchain@nightly + with: + toolchain: stable + components: rustfmt, clippy + - name: Install Rust nightly toolchain uses: dtolnay/rust-toolchain@nightly with: toolchain: nightly-2025-05-14 From efde68ed74190ef4bc227839eb92527dea0ea369 Mon Sep 17 00:00:00 2001 From: Ole Magnus Fon Johnsen Date: Mon, 22 Sep 2025 20:13:02 +0200 Subject: [PATCH 2/3] feat: add insert api --- soavec/src/lib.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/soavec/src/lib.rs b/soavec/src/lib.rs index b84a725..94d3b3b 100644 --- a/soavec/src/lib.rs +++ b/soavec/src/lib.rs @@ -807,6 +807,82 @@ impl SoAVec { } } + /// Inserts an element at position `index` within the vector, shifting all + /// elements after it to the right. + /// + /// # Panics + /// + /// Panics if `index > len`. + /// + /// # Examples + /// + /// ``` + /// use soavec::soavec; + /// + /// let mut vec = soavec![('a', 'b'), ('c', 'd')].unwrap(); + /// vec.insert(1, ('x', 'y')).unwrap(); + /// assert_eq!(vec.len(), 3); + /// ``` + pub fn insert(&mut self, index: u32, element: T) -> Result<(), AllocError> { + let _ = self.insert_mut(index, element)?; + + Ok(()) + } + + /// Inserts an element at position `index` within the vector, shifting all + /// elements after it to the right, and returning a reference to the new + /// element. + /// + /// # Panics + /// + /// Panics if `index > len`. + /// + /// # Examples + /// + /// ``` + /// use soavec::soavec; + /// + /// let mut vec = soavec![(1, 1), (3, 3), (5, 5), (7, 7), (9, 9)].unwrap(); + /// let (mut x1, _) = vec.insert_mut(3, (6, 6)).unwrap(); + /// *x1 += 1; + /// assert_eq!(vec.len(), 6); + /// assert_eq!(vec.get(3), Some((&7, &6))); + /// ``` + pub fn insert_mut(&mut self, index: u32, element: T) -> Result, AllocError> { + let len = self.len(); + + if index > len { + panic!("insertion index (is {index}) should be <= len (is {len})"); + } + + if len == self.capacity() { + // Make sure we have space to one more element. + self.buf.reserve(1)?; + } + + let ptr = self.buf.as_mut_ptr(); + let cap = self.capacity(); + + unsafe { + if index < len { + // Shift elements to the right. + let src = T::TupleRepr::get_pointers(ptr, index, cap); + let dst = T::TupleRepr::get_pointers(ptr, (index) + 1, cap); + T::TupleRepr::copy(src, dst, len - (index)); + } + + // Write the new element. + let src = T::into_tuple(element); + T::TupleRepr::write(ptr, src, index, cap); + + // Update length. + self.buf.set_len(self.len().unchecked_add(1)); + + let ptrs = T::TupleRepr::get_pointers(ptr, index, cap); + Ok(T::as_mut(PhantomData, ptrs)) + } + } + /// Clears the vector, removing all values. /// /// Note that this method has no effect on the allocated capacity @@ -1239,6 +1315,41 @@ mod tests { assert_eq!(first.a, &0); } + #[test] + fn insert_and_insert_mut() { + let mut vec = SoAVec::<(u32, u32)>::new(); + vec.push((1, 10)).unwrap(); + vec.push((3, 30)).unwrap(); + + let (first, second) = vec.insert_mut(1, (2, 20)).unwrap(); + *first += 10; + *second += 5; + + vec.insert(0, (0, 0)).unwrap(); + + let slice = vec.as_slice(); + assert_eq!(slice.0, &[0, 1, 12, 3]); + assert_eq!(slice.1, &[0, 10, 25, 30]); + } + + #[test] + fn insert_grows_capacity() { + let mut vec = SoAVec::<(u32, u32)>::with_capacity(2).unwrap(); + assert_eq!(vec.capacity(), 2); + + vec.push((1, 10)).unwrap(); + vec.push((2, 20)).unwrap(); + assert_eq!(vec.capacity(), 2); + + // Should grow capacity. + vec.insert(1, (3, 30)).unwrap(); + assert!(vec.capacity() >= 3); + + let slice = vec.as_slice(); + assert_eq!(slice.0, &[1, 3, 2]); + assert_eq!(slice.1, &[10, 30, 20]); + } + #[test] fn basic_usage_with_zst() { use soavec_derive::SoAble; From 666c9a6a7593ccd86f2f31c2deb266ea2a9de1ec Mon Sep 17 00:00:00 2001 From: Ole Magnus Fon Johnsen Date: Tue, 23 Sep 2025 11:26:09 +0200 Subject: [PATCH 3/3] feat: add insert error and remove panics --- soavec/src/lib.rs | 67 ++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/soavec/src/lib.rs b/soavec/src/lib.rs index 94d3b3b..f666a84 100644 --- a/soavec/src/lib.rs +++ b/soavec/src/lib.rs @@ -282,6 +282,29 @@ pub struct SoAVec { buf: RawSoAVec, } +/// The error type for when an index is out of bounds. +#[derive(Clone, Debug)] +pub struct IndexOutOfBoundsError; + +/// A union of possible errors that can occur when inserting into a `SoAVec`. +#[derive(Clone, Debug)] +pub enum InsertError { + IndexOutOfBounds(IndexOutOfBoundsError), + AllocError(AllocError), +} + +impl From for InsertError { + fn from(e: IndexOutOfBoundsError) -> Self { + InsertError::IndexOutOfBounds(e) + } +} + +impl From for InsertError { + fn from(e: AllocError) -> Self { + InsertError::AllocError(e) + } +} + impl SoAVec { pub fn new() -> Self { SoAVec { @@ -766,24 +789,24 @@ impl SoAVec { /// Removes and returns the element at position `index` within the vector, /// shifting all elements after it to the left. /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - /// /// # Examples /// /// ``` /// use soavec::soavec; /// /// let mut v = soavec![('a', 'a'), ('b', 'b'), ('c', 'c')].unwrap(); - /// assert_eq!(v.remove(1), ('b', 'b')); + /// assert_eq!(v.remove(1).unwrap(), ('b', 'b')); /// assert_eq!(v.len(), 2); /// ``` - pub fn remove(&mut self, index: u32) -> T { - let len = self.len(); + pub fn remove(&mut self, index: u32) -> Result { + #[cold] + fn assert_index() -> IndexOutOfBoundsError { + IndexOutOfBoundsError + } + let len = self.len(); if index >= len { - panic!("removal index (is {index}) should be < len (is {len})"); + return Err(assert_index().into()); } let cap = self.buf.capacity(); @@ -803,17 +826,13 @@ impl SoAVec { // Update the length. self.buf.set_len(len - 1); - result + Ok(result) } } /// Inserts an element at position `index` within the vector, shifting all /// elements after it to the right. /// - /// # Panics - /// - /// Panics if `index > len`. - /// /// # Examples /// /// ``` @@ -823,8 +842,8 @@ impl SoAVec { /// vec.insert(1, ('x', 'y')).unwrap(); /// assert_eq!(vec.len(), 3); /// ``` - pub fn insert(&mut self, index: u32, element: T) -> Result<(), AllocError> { - let _ = self.insert_mut(index, element)?; + pub fn insert(&mut self, index: u32, element: T) -> Result<(), InsertError> { + self.insert_mut(index, element)?; Ok(()) } @@ -833,10 +852,6 @@ impl SoAVec { /// elements after it to the right, and returning a reference to the new /// element. /// - /// # Panics - /// - /// Panics if `index > len`. - /// /// # Examples /// /// ``` @@ -848,11 +863,15 @@ impl SoAVec { /// assert_eq!(vec.len(), 6); /// assert_eq!(vec.get(3), Some((&7, &6))); /// ``` - pub fn insert_mut(&mut self, index: u32, element: T) -> Result, AllocError> { - let len = self.len(); + pub fn insert_mut(&mut self, index: u32, element: T) -> Result, InsertError> { + #[cold] + fn assert_index() -> IndexOutOfBoundsError { + IndexOutOfBoundsError + } + let len = self.len(); if index > len { - panic!("insertion index (is {index}) should be <= len (is {len})"); + return Err(assert_index().into()); } if len == self.capacity() { @@ -876,7 +895,7 @@ impl SoAVec { T::TupleRepr::write(ptr, src, index, cap); // Update length. - self.buf.set_len(self.len().unchecked_add(1)); + self.buf.set_len(len + 1); let ptrs = T::TupleRepr::get_pointers(ptr, index, cap); Ok(T::as_mut(PhantomData, ptrs)) @@ -1587,7 +1606,7 @@ mod tests { assert_eq!(foo.len(), 10); - let removed = foo.remove(4); + let removed = foo.remove(4).unwrap(); assert_eq!(removed, Foo { a: 4, b: 4 }); assert_eq!(foo.len(), 9); }