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
134 changes: 98 additions & 36 deletions library/alloc/src/collections/vec_deque/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,21 @@ impl<T: Clone, A: Allocator + Clone> Clone for VecDeque<T, A> {
}
}

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T, A: Allocator> Drop for VecDeque<T, A> {
fn drop(&mut self) {
/// Runs the destructor for all items in the slice when it gets dropped (normally or
/// during unwinding).
struct Dropper<'a, T>(&'a mut [T]);
/// Runs the destructor for all items in the slice when it gets dropped (normally or
/// during unwinding).
struct Dropper<'a, T>(&'a mut [T]);

impl<'a, T> Drop for Dropper<'a, T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(self.0);
}
}
impl<T> Drop for Dropper<'_, T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(self.0);
}
}
}

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T, A: Allocator> Drop for VecDeque<T, A> {
fn drop(&mut self) {
let (front, back) = self.as_mut_slices();
unsafe {
let _back_dropper = Dropper(back);
Expand Down Expand Up @@ -1433,18 +1433,6 @@ impl<T, A: Allocator> VecDeque<T, A> {
#[doc(alias = "retain_front")]
#[stable(feature = "deque_extras", since = "1.16.0")]
pub fn truncate(&mut self, len: usize) {
/// Runs the destructor for all items in the slice when it gets dropped (normally or
/// during unwinding).
struct Dropper<'a, T>(&'a mut [T]);

impl<'a, T> Drop for Dropper<'a, T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(self.0);
}
}
}

// Safe because:
//
// * Any slice passed to `drop_in_place` is valid; the second case has
Expand Down Expand Up @@ -1499,18 +1487,6 @@ impl<T, A: Allocator> VecDeque<T, A> {
#[doc(alias = "truncate_front")]
#[stable(feature = "vec_deque_truncate_front", since = "CURRENT_RUSTC_VERSION")]
pub fn retain_back(&mut self, len: usize) {
/// Runs the destructor for all items in the slice when it gets dropped (normally or
/// during unwinding).
struct Dropper<'a, T>(&'a mut [T]);

impl<'a, T> Drop for Dropper<'a, T> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(self.0);
}
}
}

unsafe {
if len >= self.len {
// No action is taken
Expand Down Expand Up @@ -1543,6 +1519,92 @@ impl<T, A: Allocator> VecDeque<T, A> {
}
}

/// Shortens the deque to the elements within `range`, dropping the rest.
///
/// # Panics
///
/// Panics if the starting point is greater than the end point or if
/// the end point is greater than the length of the deque.
///
/// # Examples
///
/// ```
/// # #![feature(vec_deque_retain_range)]
/// use std::collections::VecDeque;
///
/// let mut buf: VecDeque<_> = (0..6).collect();
/// buf.truncate_to_range(2..5);
/// assert_eq!(buf, [2, 3, 4]);
/// ```
#[unstable(feature = "vec_deque_retain_range", issue = "156215")]
pub fn truncate_to_range<R>(&mut self, range: R)
where
R: RangeBounds<usize>,
{
let Range { start, end } = slice::range(range, ..self.len);

if start == 0 && end == self.len {
return;
} else if start == end {
self.clear();
return;
} else if start == 0 {
self.truncate(end);
return;
} else if end == self.len {
self.retain_back(self.len - start);
return;
}

// Both the dropped prefix [0..start) and the dropped suffix [end..self.len) are
// non-empty. Plan up to three physical slices to drop, then update head/len, then
// drop. Only one of the dropped prefix or dropped suffix can cross between slices.
let (front, back) = self.as_mut_slices();
let flen = front.len();
let blen = back.len();
let fptr = front.as_mut_ptr();
let bptr = back.as_mut_ptr();

unsafe {
let (drop_a, drop_b, drop_c) = if end <= flen {

@qaijuang qaijuang May 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it make sense to add a fast path for types that don’t need dropping? Something like:

around line 1493

if !mem::needs_drop::<T>() {
    self.head = self.to_physical_idx(start);
    self.len = end - start;
    return;
}

what do you think ?

View changes since the review

// Kept range lies in `front`. The dropped suffix is the rest of `front`
// plus all of `back`.
let pre = ptr::slice_from_raw_parts_mut(fptr, start);
let mid = ptr::slice_from_raw_parts_mut(fptr.add(end), flen - end);
(pre, mid, Some(back as *mut [T]))
} else if start >= flen {
// Kept range lies in `back`. The dropped prefix is all of `front` plus the
// start of `back`.
let mid = ptr::slice_from_raw_parts_mut(bptr, start - flen);
let suf = ptr::slice_from_raw_parts_mut(bptr.add(end - flen), blen - (end - flen));
(front as *mut [T], mid, Some(suf))
} else {
// Kept range straddles the boundary. The dropped prefix is in `front`, the
// dropped suffix is in `back`. Only two regions to drop.
let pre = ptr::slice_from_raw_parts_mut(fptr, start);
let suf = ptr::slice_from_raw_parts_mut(bptr.add(end - flen), blen - (end - flen));
(pre, suf, None)
};

// Set these once only, then drop. If we called truncate + retain_back, a panic in
// a destructor could leave this truncation in a half completed state.
self.head = self.to_wrapped_index(start);
self.len = end - start;

match drop_c {
Some(c) => {
let _g_a = Dropper(&mut *drop_a);
let _g_b = Dropper(&mut *drop_b);
ptr::drop_in_place(c);

@qaijuang qaijuang May 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

without dropper, won't this leak on panic ? same for line 1537

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where would it panic? _g_{a,b} will only have their destructors run after we already attempt to drop c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Right, i see

}
None => {
let _g_a = Dropper(&mut *drop_a);
ptr::drop_in_place(drop_b);
}
}
}
}

/// Returns a reference to the underlying allocator.
#[unstable(feature = "allocator_api", issue = "32838")]
#[inline]
Expand Down
57 changes: 30 additions & 27 deletions library/alloctests/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
// tidy-alphabetical-start
#![allow(internal_features)]
#![deny(implicit_provenance_casts)]
#![deny(unsafe_op_in_unsafe_fn)]
#![feature(allocator_api)]
#![feature(binary_heap_drain_sorted)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(binary_heap_pop_if)]
#![feature(const_heap)]
#![feature(deque_extend_front)]
#![feature(iter_array_chunks)]
#![feature(casefold)]
#![feature(cow_is_borrowed)]
#![feature(const_btree_len)]
#![feature(const_heap)]
#![feature(const_trait_impl)]
#![feature(core_intrinsics)]
#![feature(cow_is_borrowed)]
#![feature(deque_extend_front)]
#![feature(downcast_unchecked)]
#![feature(drain_keep_rest)]
#![feature(exact_size_is_empty)]
#![feature(hashmap_internals)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_array_chunks)]
#![feature(iter_next_chunk)]
#![feature(linked_list_cursors)]
#![feature(local_waker)]
#![feature(macro_metavar_expr_concat)]
#![feature(map_try_insert)]
#![feature(pattern)]
#![feature(trusted_len)]
#![feature(try_reserve_kind)]
#![feature(try_with_capacity)]
#![feature(unboxed_closures)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(binary_heap_drain_sorted)]
#![feature(slice_ptr_get)]
#![feature(slice_range)]
#![feature(ptr_cast_slice)]
#![feature(slice_partial_sort_unstable)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_next_chunk)]
#![feature(slice_partition_dedup)]
#![feature(slice_ptr_get)]
#![feature(slice_range)]
#![feature(str_as_str)]
#![feature(strict_provenance_lints)]
#![feature(string_from_utf8_lossy_owned)]
#![feature(string_remove_matches)]
#![feature(const_btree_len)]
#![feature(const_trait_impl)]
#![feature(string_replace_in_place)]
#![feature(test)]
#![feature(thin_box)]
#![feature(drain_keep_rest)]
#![feature(local_waker)]
#![feature(str_as_str)]
#![feature(strict_provenance_lints)]
#![feature(string_replace_in_place)]
#![feature(trusted_len)]
#![feature(try_reserve_kind)]
#![feature(try_with_capacity)]
#![feature(unboxed_closures)]
#![feature(unique_rc_arc)]
#![feature(macro_metavar_expr_concat)]
#![feature(vec_deque_retain_range)]
#![feature(vec_peek_mut)]
#![feature(vec_try_remove)]
#![feature(ptr_cast_slice)]
#![allow(internal_features)]
#![deny(implicit_provenance_casts)]
#![deny(unsafe_op_in_unsafe_fn)]
// tidy-alphabetical-end

extern crate alloc;

Expand Down
135 changes: 135 additions & 0 deletions library/alloctests/tests/vec_deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::collections::vec_deque::Drain;
use std::fmt::Debug;
use std::ops::Bound::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicUsize, Ordering};

use Taggy::*;
use Taggypar::*;
Expand Down Expand Up @@ -2360,3 +2361,137 @@ fn test_splice_wrapping_and_resize() {

assert_eq!(Vec::from(vec), [1, 2, 3, 4, 1, 1, 1, 1, 1])
}

#[test]
fn truncate_to_range_basic() {
// no-op
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(..);
assert_eq!(v, [0, 1, 2, 3, 4, 5]);

// clear
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(3..3);
assert_eq!(v, [] as [i32; 0]);

// truncate
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(..3);
assert_eq!(v, [0, 1, 2]);

// truncate front
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(2..);
assert_eq!(v, [2, 3, 4, 5]);

let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(2..5);
assert_eq!(v, [2, 3, 4]);

let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(3..=5);
assert_eq!(v, [3, 4, 5]);

let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(..=3);
assert_eq!(v, [0, 1, 2, 3]);
}

fn make_wrapped() -> VecDeque<i32> {
let mut v = VecDeque::new();
v.extend(0..5);
v.push_front(-1);
v.push_front(-2);
v.push_front(-3);
assert_eq!(v.as_slices(), ([-3, -2, -1].as_slice(), [0, 1, 2, 3, 4].as_slice()));
v
}

#[test]
fn truncate_to_range_kept_in_front() {
let mut v = make_wrapped();
v.truncate_to_range(1..3);
assert_eq!(v, [-2, -1]);
}

#[test]
fn truncate_to_range_kept_in_back() {
let mut v = make_wrapped();
v.truncate_to_range(4..7);
assert_eq!(v, [1, 2, 3]);
}

#[test]
fn truncate_to_range_kept_straddles() {
let mut v = make_wrapped();
v.truncate_to_range(1..6);
assert_eq!(v, [-2, -1, 0, 1, 2]);
}

#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn truncate_to_range_leak() {
struct_with_counted_drop!(D(bool), DROPS => |this: &D| if this.0 { panic!("panic in `drop`"); } );

let mut q = VecDeque::new();
q.push_back(D(true));
q.push_back(D(false));
q.push_back(D(false));
q.push_back(D(false));
q.push_back(D(false));
q.push_front(D(false));
q.push_front(D(false));
q.push_front(D(false));

catch_unwind(AssertUnwindSafe(|| q.truncate_to_range(4..7))).ok();

assert_eq!(DROPS.get(), 5);
}

#[test]
fn truncate_to_range_calls_drop() {
static DROPPED: AtomicUsize = AtomicUsize::new(0);

#[derive(Debug)]
struct Foo(u8);

impl Drop for Foo {
fn drop(&mut self) {
DROPPED.fetch_add(1, Ordering::Relaxed);
}
}

let mut deque: VecDeque<_> = (0..12).map(Foo).collect();
deque.truncate_to_range(1..5);
assert!(deque.iter().map(|x| x.0).eq([1, 2, 3, 4]));
assert_eq!(8, DROPPED.load(Ordering::Relaxed));
}

#[test]
#[should_panic]
fn truncate_to_range_start_greater_than_end() {
let mut v: VecDeque<_> = (0..6).collect();
#[allow(clippy::reversed_empty_ranges)]
v.truncate_to_range(4..2);
}

#[test]
#[should_panic]
fn truncate_to_range_end_past_len() {
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(2..7);
}

#[test]
#[should_panic]
fn truncate_to_range_start_past_len() {
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(7..8);
}

#[test]
#[should_panic]
fn truncate_to_range_inclusive_end_overflow() {
let mut v: VecDeque<_> = (0..6).collect();
v.truncate_to_range(0..=usize::MAX);
}
Loading