From c2ffd3d09bd521e7332bc2d2c6da47161012f33f Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Mon, 15 Dec 2025 02:17:08 +0300 Subject: [PATCH 1/4] Add `Vec::remove_insert()` In patterns where an insertion directly follows a removal, this serves as being more efficient, since it only shifts/copies values as needed for the combined operation. This also comes with the added bonus of not needing to check for fullness, and thus, a `Result` is not needed as a return type. Signed-off-by: Mohammad AlSaleh --- CHANGELOG.md | 1 + src/vec/mod.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d65933181..0585240b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - Added `from_bytes_truncating_at_nul` to `CString` +- Added `remove_insert` to `Vec` ## [v0.9.2] 2025-11-12 diff --git a/src/vec/mod.rs b/src/vec/mod.rs index 448b22db34..c54148998e 100644 --- a/src/vec/mod.rs +++ b/src/vec/mod.rs @@ -960,6 +960,56 @@ impl + ?Sized> VecInner { value } + /// This is the same as calling [`Vec::remove`] with `remove_index` + /// followed by calling [`Vec::insert`] with `insert_index` and `element`. + /// + /// The returned value is the removed element. + /// + /// This is more efficient than removing then inserting since it only shifts + /// `remove_index.abs_diff(insert_index)` values. + /// + /// [`remove`]: Vec::remove + /// [`insert`]: Vec::insert + /// + /// # Panics + /// + /// Panics if `remove_index` or `insert_index` are out of bounds. + /// + /// # Examples + /// + /// ``` + /// use heapless::Vec; + /// + /// let mut v: Vec<_, 8> = Vec::from_slice(&[0, 1, 2, 3]).unwrap(); + /// assert_eq!(v.remove_insert(1, 2, 4), 1); + /// // only one element (2) is shifted back + /// assert_eq!(v, [0, 2, 4, 3]); + /// ``` + pub fn remove_insert(&mut self, remove_index: usize, insert_index: usize, element: T) -> T { + let length = self.len(); + + assert!(remove_index < length); + assert!(insert_index < length); + + unsafe { + let to_remove = ptr::read(self.as_ptr().add(remove_index)); + + match remove_index.cmp(&insert_index) { + Ordering::Equal => (), + Ordering::Less => { + let remove_at = self.as_mut_ptr().add(remove_index); + ptr::copy(remove_at.add(1), remove_at, insert_index - remove_index); + } + Ordering::Greater => { + let insert_at = self.as_mut_ptr().add(insert_index); + ptr::copy(insert_at, insert_at.add(1), remove_index - insert_index); + } + } + ptr::write(self.as_mut_ptr().add(insert_index), element); + to_remove + } + } + /// Returns true if the vec is full pub fn is_full(&self) -> bool { self.len() == self.capacity() @@ -2119,6 +2169,92 @@ mod tests { assert_eq!(v.len(), 0); } + #[test] + fn remove_insert() { + let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut v: Vec = Vec::from_array(arr); + let mut v2: Vec = Vec::from_array(arr); + + // insert_index == remove_index + let n = v.remove_insert(2, 2, 10); + assert_eq!(n, 2); + assert_eq!(v, [0, 1, 10, 3, 4, 5, 6, 7, 8, 9]); + + let n2 = v2.remove(2); + v2.insert(2, 10).unwrap(); + assert_eq!(n, n2); + assert_eq!(v, v2); + + // reset + v.copy_from_slice(&arr); + v2.copy_from_slice(&arr); + + // insert_index > remove_index + let n = v.remove_insert(3, 5, 10); + assert_eq!(n, 3); + assert_eq!(v, [0, 1, 2, 4, 5, 10, 6, 7, 8, 9]); + + let n2 = v2.remove(3); + v2.insert(5, 10).unwrap(); + assert_eq!(n, n2); + assert_eq!(v, v2); + + v.copy_from_slice(&arr); + v2.copy_from_slice(&arr); + + // insert_index < remove_index + let n = v.remove_insert(5, 3, 10); + assert_eq!(n, 5); + assert_eq!(v, [0, 1, 2, 10, 3, 4, 6, 7, 8, 9]); + + let n2 = v2.remove(5); + v2.insert(3, 10).unwrap(); + + assert_eq!(n, n2); + assert_eq!(v, v2); + + // at boundaries + + v.copy_from_slice(&arr); + v2.copy_from_slice(&arr); + + let n = v.remove_insert(0, 9, 10); + assert_eq!(n, 0); + assert_eq!(v, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + let n2 = v2.remove(0); + v2.insert(9, 10).unwrap(); + assert_eq!(n, n2); + assert_eq!(v, v2); + + v.copy_from_slice(&arr); + v2.copy_from_slice(&arr); + + let n = v.remove_insert(9, 0, 10); + assert_eq!(n, 9); + assert_eq!(v, [10, 0, 1, 2, 3, 4, 5, 6, 7, 8]); + + let n2 = v2.remove(9); + v2.insert(0, 10).unwrap(); + assert_eq!(n, n2); + assert_eq!(v, v2); + } + + #[test] + #[should_panic] + fn remove_insert_out_of_bounds() { + let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut v: Vec = Vec::from_array(arr); + let _ = v.remove_insert(0, 10, 10); + } + + #[test] + #[should_panic] + fn remove_insert_empty() { + let mut v: Vec = Vec::from_array([]); + let _ = v.remove_insert(0, 0, 10); + } + #[test] fn resize_size_limit() { let mut v: Vec = Vec::new(); From 58a59a58662293ae2afa46c6e67414b7329edfef Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Tue, 16 Dec 2025 02:39:44 +0300 Subject: [PATCH 2/4] Document invariants for unsafety calls in Vec::remove_insert() Signed-off-by: Mohammad AlSaleh --- src/vec/mod.rs | 63 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/src/vec/mod.rs b/src/vec/mod.rs index c54148998e..73ce312c53 100644 --- a/src/vec/mod.rs +++ b/src/vec/mod.rs @@ -991,23 +991,56 @@ impl + ?Sized> VecInner { assert!(remove_index < length); assert!(insert_index < length); - unsafe { - let to_remove = ptr::read(self.as_ptr().add(remove_index)); - - match remove_index.cmp(&insert_index) { - Ordering::Equal => (), - Ordering::Less => { - let remove_at = self.as_mut_ptr().add(remove_index); - ptr::copy(remove_at.add(1), remove_at, insert_index - remove_index); - } - Ordering::Greater => { - let insert_at = self.as_mut_ptr().add(insert_index); - ptr::copy(insert_at, insert_at.add(1), remove_index - insert_index); - } + // SAFETY: `remove_index < length` assertion guarantees the pointer is within bounds. + let to_remove = unsafe { ptr::read(self.as_ptr().add(remove_index)) }; + + match remove_index.cmp(&insert_index) { + Ordering::Equal => (), + Ordering::Less => { + // SAFETY: `remove_index < length` assertion guarantees the pointer is within + // bounds. + let remove_at = unsafe { self.as_mut_ptr().add(remove_index) }; + + // SAFETY: + // Copies from + // (remove_index + 1)..(insert_index + 1) + // to + // remove_index..insert_index + // + // remove_index < insert_index (this match) + // remove_index + 1 < insert_index + 1 (can't saturate because <= length) + // remove_index + 1 <= insert_index < length + // insert_index + 1 <= length + // + // If `remove_index == 0`, and `insert_index + 1 == length`, then we copy from + // 1..length to 0..(length-1). + unsafe { ptr::copy(remove_at.add(1), remove_at, insert_index - remove_index) }; + } + Ordering::Greater => { + // SAFETY: `insert_index < length` assertion guarantees the pointer is within + // bounds. + let insert_at = unsafe { self.as_mut_ptr().add(insert_index) }; + + // SAFETY: + // Copies from + // insert_index..remove_index + // to + // (insert_index + 1)..(remove_index + 1) + // + // insert_index < remove_index (this match) + // insert_index + 1 < remove_index + 1 (can't saturate because <= length) + // insert_index + 1 <= remove_index < length + // remove_index + 1 <= length + // + // If `insert_index == 0`, and `remove_index + 1 == length`, then we copy from + // 0..(length-1) to 1..(length). + unsafe { ptr::copy(insert_at, insert_at.add(1), remove_index - insert_index) }; } - ptr::write(self.as_mut_ptr().add(insert_index), element); - to_remove } + + // SAFETY: `insert_index < length` assertion guarantees the pointer is within bounds. + unsafe { ptr::write(self.as_mut_ptr().add(insert_index), element) }; + to_remove } /// Returns true if the vec is full From 9c6d76e69b8e933591369df5d69596a8fd3c1f17 Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Wed, 17 Dec 2025 15:56:28 +0300 Subject: [PATCH 3/4] use slice rotate methods Signed-off-by: Mohammad AlSaleh --- src/vec/mod.rs | 49 +++---------------------------------------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/src/vec/mod.rs b/src/vec/mod.rs index 73ce312c53..18850a4c57 100644 --- a/src/vec/mod.rs +++ b/src/vec/mod.rs @@ -991,56 +991,13 @@ impl + ?Sized> VecInner { assert!(remove_index < length); assert!(insert_index < length); - // SAFETY: `remove_index < length` assertion guarantees the pointer is within bounds. - let to_remove = unsafe { ptr::read(self.as_ptr().add(remove_index)) }; - match remove_index.cmp(&insert_index) { Ordering::Equal => (), - Ordering::Less => { - // SAFETY: `remove_index < length` assertion guarantees the pointer is within - // bounds. - let remove_at = unsafe { self.as_mut_ptr().add(remove_index) }; - - // SAFETY: - // Copies from - // (remove_index + 1)..(insert_index + 1) - // to - // remove_index..insert_index - // - // remove_index < insert_index (this match) - // remove_index + 1 < insert_index + 1 (can't saturate because <= length) - // remove_index + 1 <= insert_index < length - // insert_index + 1 <= length - // - // If `remove_index == 0`, and `insert_index + 1 == length`, then we copy from - // 1..length to 0..(length-1). - unsafe { ptr::copy(remove_at.add(1), remove_at, insert_index - remove_index) }; - } - Ordering::Greater => { - // SAFETY: `insert_index < length` assertion guarantees the pointer is within - // bounds. - let insert_at = unsafe { self.as_mut_ptr().add(insert_index) }; - - // SAFETY: - // Copies from - // insert_index..remove_index - // to - // (insert_index + 1)..(remove_index + 1) - // - // insert_index < remove_index (this match) - // insert_index + 1 < remove_index + 1 (can't saturate because <= length) - // insert_index + 1 <= remove_index < length - // remove_index + 1 <= length - // - // If `insert_index == 0`, and `remove_index + 1 == length`, then we copy from - // 0..(length-1) to 1..(length). - unsafe { ptr::copy(insert_at, insert_at.add(1), remove_index - insert_index) }; - } + Ordering::Less => self[remove_index..=insert_index].rotate_left(1), + Ordering::Greater => self[insert_index..=remove_index].rotate_right(1), } - // SAFETY: `insert_index < length` assertion guarantees the pointer is within bounds. - unsafe { ptr::write(self.as_mut_ptr().add(insert_index), element) }; - to_remove + mem::replace(&mut self[insert_index], element) } /// Returns true if the vec is full From d8816a746170cf5bb91cc06d0f6c87e585e79036 Mon Sep 17 00:00:00 2001 From: Mohammad AlSaleh Date: Sun, 21 Dec 2025 02:35:26 +0300 Subject: [PATCH 4/4] Revert "use slice rotate methods" It's slower than not combining remove/insert. This reverts commit 9c6d76e69b8e933591369df5d69596a8fd3c1f17. --- src/vec/mod.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/vec/mod.rs b/src/vec/mod.rs index 18850a4c57..73ce312c53 100644 --- a/src/vec/mod.rs +++ b/src/vec/mod.rs @@ -991,13 +991,56 @@ impl + ?Sized> VecInner { assert!(remove_index < length); assert!(insert_index < length); + // SAFETY: `remove_index < length` assertion guarantees the pointer is within bounds. + let to_remove = unsafe { ptr::read(self.as_ptr().add(remove_index)) }; + match remove_index.cmp(&insert_index) { Ordering::Equal => (), - Ordering::Less => self[remove_index..=insert_index].rotate_left(1), - Ordering::Greater => self[insert_index..=remove_index].rotate_right(1), + Ordering::Less => { + // SAFETY: `remove_index < length` assertion guarantees the pointer is within + // bounds. + let remove_at = unsafe { self.as_mut_ptr().add(remove_index) }; + + // SAFETY: + // Copies from + // (remove_index + 1)..(insert_index + 1) + // to + // remove_index..insert_index + // + // remove_index < insert_index (this match) + // remove_index + 1 < insert_index + 1 (can't saturate because <= length) + // remove_index + 1 <= insert_index < length + // insert_index + 1 <= length + // + // If `remove_index == 0`, and `insert_index + 1 == length`, then we copy from + // 1..length to 0..(length-1). + unsafe { ptr::copy(remove_at.add(1), remove_at, insert_index - remove_index) }; + } + Ordering::Greater => { + // SAFETY: `insert_index < length` assertion guarantees the pointer is within + // bounds. + let insert_at = unsafe { self.as_mut_ptr().add(insert_index) }; + + // SAFETY: + // Copies from + // insert_index..remove_index + // to + // (insert_index + 1)..(remove_index + 1) + // + // insert_index < remove_index (this match) + // insert_index + 1 < remove_index + 1 (can't saturate because <= length) + // insert_index + 1 <= remove_index < length + // remove_index + 1 <= length + // + // If `insert_index == 0`, and `remove_index + 1 == length`, then we copy from + // 0..(length-1) to 1..(length). + unsafe { ptr::copy(insert_at, insert_at.add(1), remove_index - insert_index) }; + } } - mem::replace(&mut self[insert_index], element) + // SAFETY: `insert_index < length` assertion guarantees the pointer is within bounds. + unsafe { ptr::write(self.as_mut_ptr().add(insert_index), element) }; + to_remove } /// Returns true if the vec is full