Skip to content
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
126 changes: 126 additions & 0 deletions src/vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,46 @@ impl<T, LenT: LenType, S: VecStorage<T> + ?Sized> VecInner<T, LenT, S> {
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 {

Choose a reason for hiding this comment

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

How about?

fn remove_insert(&mut self, remove_index: usize, insert_index: usize, element: T) -> T {
    if remove_index < insert_index {
        self[remove_index..=insert_index].rotate_left(1);
    } else if insert_index < remove_index {
        self[insert_index..=remove_index].rotate_right(1);
    }
    mem::replace(&mut self[insert_index], element)
}

Copy link
Contributor

@sgued sgued Dec 17, 2025

Choose a reason for hiding this comment

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

A benchmark would be welcome to compare both implementations. Given the complexity of the implementation of rotate_left and rotate_right, I'm convinced it would be slower and larger in code size.

Copy link
Author

Choose a reason for hiding this comment

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

I wrote the comment below before seeing this conversation.

Copy link
Author

Choose a reason for hiding this comment

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

A benchmark would be welcome to compare both implementations. Given the complexity of the implementation of rotate_left and rotate_right, I'm convinced it would be slower and larger in code size.

Yep. It's even slower that romove()+insert().

https://github.com/MoSal/bench-insert-remove

remove_insert_rotate u16    time:   [42.533 ms 42.662 ms 42.813 ms]
remove and insert u16       time:   [31.312 ms 31.379 ms 31.456 ms]
remove_insert_unsafe u16    time:   [16.316 ms 16.383 ms 16.474 ms]

remove_insert_rotate u8     time:   [55.221 ms 55.435 ms 55.695 ms]
remove and insert u8        time:   [46.525 ms 46.745 ms 47.026 ms]
remove_insert_unsafe u8     time:   [36.480 ms 36.582 ms 36.701 ms]

The test applies the Move-To-Front transform then the reverse on random u16 and u8 buffers. The u8 benches only differ in input buffer being larger, and memchr::memchr() being used instead of .iter().position().

let length = self.len();

assert!(remove_index < length);
assert!(insert_index < length);

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),
}

mem::replace(&mut self[insert_index], element)
}

/// Returns true if the vec is full
pub fn is_full(&self) -> bool {
self.len() == self.capacity()
Expand Down Expand Up @@ -2119,6 +2159,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<u8, 10> = Vec::from_array(arr);
let mut v2: Vec<u8, 10> = 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<u8, 10> = Vec::from_array(arr);
let _ = v.remove_insert(0, 10, 10);
}

#[test]
#[should_panic]
fn remove_insert_empty() {
let mut v: Vec<u8, 10> = Vec::from_array([]);
let _ = v.remove_insert(0, 0, 10);
}

#[test]
fn resize_size_limit() {
let mut v: Vec<u8, 4> = Vec::new();
Expand Down
Loading