add VecDeque::splice

This commit is contained in:
Jeremy Smart 2025-10-01 18:29:30 -04:00
parent 7e7280f24b
commit 0763954226
No known key found for this signature in database
GPG key ID: 5AD8086D5DF29A8F
6 changed files with 299 additions and 13 deletions

View file

@ -21,13 +21,13 @@ pub struct Drain<
> {
// We can't just use a &mut VecDeque<T, A>, as that would make Drain invariant over T
// and we want it to be covariant instead
deque: NonNull<VecDeque<T, A>>,
pub(super) deque: NonNull<VecDeque<T, A>>,
// drain_start is stored in deque.len
drain_len: usize,
pub(super) drain_len: usize,
// index into the logical array, not the physical one (always lies in [0..deque.len))
idx: usize,
// number of elements remaining after dropping the drain
new_len: usize,
// number of elements after the drained range
pub(super) tail_len: usize,
remaining: usize,
// Needed to make Drain covariant over T
_marker: PhantomData<&'a T>,
@ -40,12 +40,12 @@ impl<'a, T, A: Allocator> Drain<'a, T, A> {
drain_len: usize,
) -> Self {
let orig_len = mem::replace(&mut deque.len, drain_start);
let new_len = orig_len - drain_len;
let tail_len = orig_len - drain_start - drain_len;
Drain {
deque: NonNull::from(deque),
drain_len,
idx: drain_start,
new_len,
tail_len,
remaining: drain_len,
_marker: PhantomData,
}
@ -78,7 +78,7 @@ impl<T: fmt::Debug, A: Allocator> fmt::Debug for Drain<'_, T, A> {
f.debug_tuple("Drain")
.field(&self.drain_len)
.field(&self.idx)
.field(&self.new_len)
.field(&self.tail_len)
.field(&self.remaining)
.finish()
}
@ -125,7 +125,9 @@ impl<T, A: Allocator> Drop for Drain<'_, T, A> {
let source_deque = unsafe { self.0.deque.as_mut() };
let drain_len = self.0.drain_len;
let new_len = self.0.new_len;
let head_len = source_deque.len; // #elements in front of the drain
let tail_len = self.0.tail_len; // #elements behind the drain
let new_len = head_len + tail_len;
if T::IS_ZST {
// no need to copy around any memory if T is a ZST
@ -133,9 +135,6 @@ impl<T, A: Allocator> Drop for Drain<'_, T, A> {
return;
}
let head_len = source_deque.len; // #elements in front of the drain
let tail_len = new_len - head_len; // #elements behind the drain
// Next, we will fill the hole left by the drain with as few writes as possible.
// The code below handles the following control flow and reduces the amount of
// branches under the assumption that `head_len == 0 || tail_len == 0`, i.e.
@ -219,7 +218,7 @@ impl<T, A: Allocator> Drop for Drain<'_, T, A> {
}
if new_len == 0 {
// Special case: If the entire dequeue was drained, reset the head back to 0,
// Special case: If the entire deque was drained, reset the head back to 0,
// like `.clear()` does.
source_deque.head = 0;
} else if head_len < tail_len {

View file

@ -62,6 +62,13 @@ use self::spec_from_iter::SpecFromIter;
mod spec_from_iter;
#[cfg(not(no_global_oom_handling))]
#[unstable(feature = "deque_extend_front", issue = "146975")]
pub use self::splice::Splice;
#[cfg(not(no_global_oom_handling))]
mod splice;
#[cfg(test)]
mod tests;
@ -1838,6 +1845,64 @@ impl<T, A: Allocator> VecDeque<T, A> {
unsafe { Drain::new(self, drain_start, drain_len) }
}
/// Creates a splicing iterator that replaces the specified range in the deque with the given
/// `replace_with` iterator and yields the removed items. `replace_with` does not need to be the
/// same length as `range`.
///
/// `range` is removed even if the `Splice` iterator is not consumed before it is dropped.
///
/// It is unspecified how many elements are removed from the deque if the `Splice` value is
/// leaked.
///
/// The input iterator `replace_with` is only consumed when the `Splice` value is dropped.
///
/// This is optimal if:
///
/// * The tail (elements in the deque after `range`) is empty,
/// * or `replace_with` yields fewer or equal elements than `range`'s length
/// * or the lower bound of its `size_hint()` is exact.
///
/// Otherwise, a temporary vector is allocated and the tail is moved twice.
///
/// # Panics
///
/// Panics if the range has `start_bound > end_bound`, or, if the range is
/// bounded on either end and past the length of the deque.
///
/// # Examples
///
/// ```
/// # #![feature(deque_extend_front)]
/// # use std::collections::VecDeque;
///
/// let mut v = VecDeque::from(vec![1, 2, 3, 4]);
/// let new = [7, 8, 9];
/// let u: Vec<_> = v.splice(1..3, new).collect();
/// assert_eq!(v, [1, 7, 8, 9, 4]);
/// assert_eq!(u, [2, 3]);
/// ```
///
/// Using `splice` to insert new items into a vector efficiently at a specific position
/// indicated by an empty range:
///
/// ```
/// # #![feature(deque_extend_front)]
/// # use std::collections::VecDeque;
///
/// let mut v = VecDeque::from(vec![1, 5]);
/// let new = [2, 3, 4];
/// v.splice(1..1, new);
/// assert_eq!(v, [1, 2, 3, 4, 5]);
/// ```
#[unstable(feature = "deque_extend_front", issue = "146975")]
pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter, A>
where
R: RangeBounds<usize>,
I: IntoIterator<Item = T>,
{
Splice { drain: self.drain(range), replace_with: replace_with.into_iter() }
}
/// Clears the deque, removing all values.
///
/// # Examples

View file

@ -0,0 +1,150 @@
use core::alloc::Allocator;
use crate::alloc::Global;
use crate::collections::vec_deque::Drain;
use crate::vec::Vec;
/// A splicing iterator for `VecDeque`.
///
/// This struct is created by [`VecDeque::splice()`][super::VecDeque::splice].
/// See its documentation for more.
///
/// # Example
///
/// ```
/// # #![feature(deque_extend_front)]
/// # use std::collections::VecDeque;
///
/// let mut v = VecDeque::from(vec![0, 1, 2]);
/// let new = [7, 8];
/// let iter: std::collections::vec_deque::Splice<'_, _> = v.splice(1.., new);
/// ```
#[unstable(feature = "deque_extend_front", issue = "146975")]
#[derive(Debug)]
pub struct Splice<
'a,
I: Iterator + 'a,
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + 'a = Global,
> {
pub(super) drain: Drain<'a, I::Item, A>,
pub(super) replace_with: I,
}
#[unstable(feature = "deque_extend_front", issue = "146975")]
impl<I: Iterator, A: Allocator> Iterator for Splice<'_, I, A> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.drain.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.drain.size_hint()
}
}
#[unstable(feature = "deque_extend_front", issue = "146975")]
impl<I: Iterator, A: Allocator> DoubleEndedIterator for Splice<'_, I, A> {
fn next_back(&mut self) -> Option<Self::Item> {
self.drain.next_back()
}
}
#[unstable(feature = "deque_extend_front", issue = "146975")]
impl<I: Iterator, A: Allocator> ExactSizeIterator for Splice<'_, I, A> {}
// See also: [`crate::vec::Splice`].
#[unstable(feature = "deque_extend_front", issue = "146975")]
impl<I: Iterator, A: Allocator> Drop for Splice<'_, I, A> {
fn drop(&mut self) {
// This will set drain.remaining to 0, so its drop won't try to read deallocated memory on
// drop.
self.drain.by_ref().for_each(drop);
// At this point draining is done and the only remaining tasks are splicing
// and moving things into the final place.
unsafe {
let tail_len = self.drain.tail_len; // #elements behind the drain
if tail_len == 0 {
self.drain.deque.as_mut().extend(self.replace_with.by_ref());
return;
}
// First fill the range left by drain().
if !self.drain.fill(&mut self.replace_with) {
return;
}
// There may be more elements. Use the lower bound as an estimate.
// FIXME: Is the upper bound a better guess? Or something else?
let (lower_bound, _upper_bound) = self.replace_with.size_hint();
if lower_bound > 0 {
self.drain.move_tail(lower_bound);
if !self.drain.fill(&mut self.replace_with) {
return;
}
}
// Collect any remaining elements.
// This is a zero-length vector which does not allocate if `lower_bound` was exact.
let mut collected = self.replace_with.by_ref().collect::<Vec<I::Item>>().into_iter();
// Now we have an exact count.
if collected.len() > 0 {
self.drain.move_tail(collected.len());
let filled = self.drain.fill(&mut collected);
debug_assert!(filled);
debug_assert_eq!(collected.len(), 0);
}
}
// Let `Drain::drop` move the tail back if necessary and restore `deque.len`.
}
}
/// Private helper methods for `Splice::drop`
impl<T, A: Allocator> Drain<'_, T, A> {
/// The range from `self.deque.len` to `self.deque.len + self.drain_len` contains elements that
/// have been moved out.
/// Fill that range as much as possible with new elements from the `replace_with` iterator.
/// Returns `true` if we filled the entire range. (`replace_with.next()` didnt return `None`.)
///
/// # Safety
///
/// self.deque must be valid. self.deque.len and self.deque.len + self.drain_len must be less
/// than twice the deque's capacity.
unsafe fn fill<I: Iterator<Item = T>>(&mut self, replace_with: &mut I) -> bool {
let deque = unsafe { self.deque.as_mut() };
let range_start = deque.len;
let range_end = range_start + self.drain_len;
for idx in range_start..range_end {
if let Some(new_item) = replace_with.next() {
let index = deque.to_physical_idx(idx);
unsafe { deque.buffer_write(index, new_item) };
deque.len += 1;
self.drain_len -= 1;
} else {
return false;
}
}
true
}
/// Makes room for inserting more elements before the tail.
///
/// # Safety
///
/// self.deque must be valid.
unsafe fn move_tail(&mut self, additional: usize) {
let deque = unsafe { self.deque.as_mut() };
let tail_start = deque.len + self.drain_len;
deque.buf.reserve(tail_start + self.tail_len, additional);
let new_tail_start = tail_start + additional;
unsafe {
deque.wrap_copy(tail_start, new_tail_start, self.tail_len);
}
self.drain_len += additional;
}
}

View file

@ -50,6 +50,7 @@ impl<I: Iterator, A: Allocator> DoubleEndedIterator for Splice<'_, I, A> {
#[stable(feature = "vec_splice", since = "1.21.0")]
impl<I: Iterator, A: Allocator> ExactSizeIterator for Splice<'_, I, A> {}
// See also: [`crate::collections::vec_deque::Splice`].
#[stable(feature = "vec_splice", since = "1.21.0")]
impl<I: Iterator, A: Allocator> Drop for Splice<'_, I, A> {
fn drop(&mut self) {

View file

@ -1,11 +1,11 @@
#![feature(allocator_api)]
#![feature(alloc_layout_extra)]
#![feature(deque_extend_front)]
#![feature(iter_array_chunks)]
#![feature(assert_matches)]
#![feature(wtf8_internals)]
#![feature(cow_is_borrowed)]
#![feature(core_intrinsics)]
#![feature(deque_extend_front)]
#![feature(downcast_unchecked)]
#![feature(exact_size_is_empty)]
#![feature(hashmap_internals)]

View file

@ -2155,3 +2155,74 @@ fn test_extend_front_specialization_copy_slice() {
// check it really wrapped
assert_eq!(v.as_slices(), ([5].as_slice(), [4, 3, 2].as_slice()));
}
#[test]
fn test_splice() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
let a = [10, 11, 12];
v.splice(2..4, a);
assert_eq!(v, &[1, 2, 10, 11, 12, 5]);
v.splice(1..3, Some(20));
assert_eq!(v, &[1, 20, 11, 12, 5]);
}
#[test]
fn test_splice_inclusive_range() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
let a = [10, 11, 12];
let t1: Vec<_> = v.splice(2..=3, a).collect();
assert_eq!(v, &[1, 2, 10, 11, 12, 5]);
assert_eq!(t1, &[3, 4]);
let t2: Vec<_> = v.splice(1..=2, Some(20)).collect();
assert_eq!(v, &[1, 20, 11, 12, 5]);
assert_eq!(t2, &[2, 10]);
}
#[test]
fn test_splice_inclusive_range2() {
let mut v = VecDeque::from(vec![1, 2, 10, 11, 12, 5]);
let t2: Vec<_> = v.splice(1..=2, Some(20)).collect();
assert_eq!(v, &[1, 20, 11, 12, 5]);
assert_eq!(t2, &[2, 10]);
}
#[test]
#[should_panic]
fn test_splice_out_of_bounds() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
let a = [10, 11, 12];
v.splice(5..6, a);
}
#[test]
#[should_panic]
fn test_splice_inclusive_out_of_bounds() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
let a = [10, 11, 12];
v.splice(5..=5, a);
}
#[test]
fn test_splice_items_zero_sized() {
let mut vec = VecDeque::from(vec![(), (), ()]);
let vec2 = VecDeque::from(vec![]);
let t: Vec<_> = vec.splice(1..2, vec2.iter().cloned()).collect();
assert_eq!(vec, &[(), ()]);
assert_eq!(t, &[()]);
}
#[test]
fn test_splice_unbounded() {
let mut vec = VecDeque::from(vec![1, 2, 3, 4, 5]);
let t: Vec<_> = vec.splice(.., None).collect();
assert_eq!(vec, &[]);
assert_eq!(t, &[1, 2, 3, 4, 5]);
}
#[test]
fn test_splice_forget() {
let mut v = VecDeque::from(vec![1, 2, 3, 4, 5]);
let a = [10, 11, 12];
std::mem::forget(v.splice(2..4, a));
assert_eq!(v, &[1, 2]);
}