From ec8c18fdb83bbda486ad0e175dfc407727076341 Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Sat, 2 May 2026 19:35:54 -0700 Subject: [PATCH 1/3] Un-copy-paste Dropper in VecDeque code --- .../alloc/src/collections/vec_deque/mod.rs | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 44211354f520e..80b69b1dba557 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -133,21 +133,21 @@ impl Clone for VecDeque { } } -#[stable(feature = "rust1", since = "1.0.0")] -unsafe impl<#[may_dangle] T, A: Allocator> Drop for VecDeque { - 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 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 { + fn drop(&mut self) { let (front, back) = self.as_mut_slices(); unsafe { let _back_dropper = Dropper(back); @@ -1433,18 +1433,6 @@ impl VecDeque { #[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 @@ -1499,18 +1487,6 @@ impl VecDeque { #[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 From ba313ddd1db0c8264be2f20473f561ce1942fe53 Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Sat, 2 May 2026 19:36:08 -0700 Subject: [PATCH 2/3] Implement `VecDeque::retain_range` --- .../alloc/src/collections/vec_deque/mod.rs | 86 +++++++++++ library/alloctests/tests/lib.rs | 1 + library/alloctests/tests/vec_deque.rs | 135 ++++++++++++++++++ 3 files changed, 222 insertions(+) diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 80b69b1dba557..9eac8bcde1d1c 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1519,6 +1519,92 @@ impl VecDeque { } } + /// 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(&mut self, range: R) + where + R: RangeBounds, + { + 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 { + // 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); + } + 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] diff --git a/library/alloctests/tests/lib.rs b/library/alloctests/tests/lib.rs index e5b3646f14a58..c06a22cbfb5d8 100644 --- a/library/alloctests/tests/lib.rs +++ b/library/alloctests/tests/lib.rs @@ -40,6 +40,7 @@ #![feature(vec_peek_mut)] #![feature(vec_try_remove)] #![feature(ptr_cast_slice)] +#![feature(vec_deque_retain_range)] #![allow(internal_features)] #![deny(implicit_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/library/alloctests/tests/vec_deque.rs b/library/alloctests/tests/vec_deque.rs index 2e75b7b07e63f..15cc156d6988f 100644 --- a/library/alloctests/tests/vec_deque.rs +++ b/library/alloctests/tests/vec_deque.rs @@ -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::*; @@ -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 { + 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); +} From f371a62bdf3bdf868dc29a5d9b19140ded6d35f7 Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Wed, 13 May 2026 07:42:32 -0700 Subject: [PATCH 3/3] Tidy up library/alloctests/tests/lib.rs --- library/alloctests/tests/lib.rs | 58 +++++++++++++++++---------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/library/alloctests/tests/lib.rs b/library/alloctests/tests/lib.rs index c06a22cbfb5d8..96dcde71fc071 100644 --- a/library/alloctests/tests/lib.rs +++ b/library/alloctests/tests/lib.rs @@ -1,49 +1,51 @@ +// 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(string_remove_matches)] -#![feature(const_btree_len)] -#![feature(const_trait_impl)] -#![feature(test)] -#![feature(thin_box)] -#![feature(drain_keep_rest)] -#![feature(local_waker)] +#![feature(slice_ptr_get)] +#![feature(slice_range)] #![feature(str_as_str)] #![feature(strict_provenance_lints)] +#![feature(string_remove_matches)] #![feature(string_replace_in_place)] +#![feature(test)] +#![feature(thin_box)] +#![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)] -#![feature(vec_deque_retain_range)] -#![allow(internal_features)] -#![deny(implicit_provenance_casts)] -#![deny(unsafe_op_in_unsafe_fn)] +// tidy-alphabetical-end extern crate alloc;