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
23 changes: 11 additions & 12 deletions library/alloc/src/collections/vec_deque/drain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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,
}
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -125,17 +125,16 @@ 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
source_deque.len = new_len;
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.
Expand Down Expand Up @@ -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 {
Expand Down
65 changes: 65 additions & 0 deletions library/alloc/src/collections/vec_deque/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,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;

Expand Down Expand Up @@ -1625,6 +1632,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.
Comment on lines +1646 to +1652
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a note to the tracking issue to confirm this is accurate? I'm not sure but it feels like there ought to be slightly different conditions vs. Vec, since we have a potentially 'empty' middle to play with...

///
/// # 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
Expand Down
143 changes: 143 additions & 0 deletions library/alloc/src/collections/vec_deque/splice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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> {}

#[unstable(feature = "deque_extend_front", issue = "146975")]
impl<I: Iterator, A: Allocator> Drop for Splice<'_, I, A> {
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment here and in Vec's Splice pointing at each other?

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);

Copy link
Member

Choose a reason for hiding this comment

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

Do we need something like

self.drain.iter = (&[]).iter();
here to avoid problems with Drain::drop?

// At this point draining is done and the only remaining tasks are splicing
// and moving things into the final place.

unsafe {
let source_deque = self.drain.deque.as_mut();

let tail_len = self.drain.tail_len; // #elements behind the drain

if tail_len == 0 {
source_deque.extend(self.replace_with.by_ref());
return;
}

if !self.drain.fill(&mut self.replace_with) {
Copy link
Member

Choose a reason for hiding this comment

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

This would invalidate source_deque -- can we avoid accidental mis-use by removing that variable, inlining the as_mut() above?

Copy link
Member

Choose a reason for hiding this comment

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

Also, can you restore the comment from Vec ("First fill the range left by drain().")?

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 true {
// if !self.drain.fill(tail_start, &mut self.replace_with) {
Comment on lines +85 to +86
Copy link
Member

Choose a reason for hiding this comment

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

Why this change/comment?

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()` didn’t return `None`.)
unsafe fn fill<I: Iterator<Item = T>>(&mut self, replace_with: &mut I) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

What's the safety precondition on this method?

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 = unsafe { self.deque.as_ref() }.to_physical_idx(idx);
unsafe { self.deque.as_mut().buffer_write(index, new_item) };
deque.len += 1;
Copy link
Member

Choose a reason for hiding this comment

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

I'd expect this to cause UB -- self.deque.as_mut() has invalidated the earlier deque &mut already at this point?

self.drain_len -= 1;
} else {
return false;
}
}
true
}

/// Makes room for inserting more elements before the tail.
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);
Copy link
Member

Choose a reason for hiding this comment

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

Do we have coverage for this reserve running? Normally growing VecDeque requires extra logic AFAICT, not just reserving the underlying buffer.


let new_tail_start = tail_start + additional;
unsafe {
deque.wrap_copy(tail_start, new_tail_start, self.tail_len);
}
self.drain_len += additional;
}
}
1 change: 1 addition & 0 deletions library/alloctests/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(allocator_api)]
#![feature(alloc_layout_extra)]
#![feature(deque_extend_front)]
#![feature(iter_array_chunks)]
#![feature(assert_matches)]
#![feature(wtf8_internals)]
Expand Down
71 changes: 71 additions & 0 deletions library/alloctests/tests/vec_deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1849,3 +1849,74 @@ fn test_truncate_front() {
v.truncate_front(5);
assert_eq!(v.as_slices(), ([2, 3, 4, 5, 6].as_slice(), [].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]);
}
Loading