Skip to content

Commit

Permalink
Auto merge of #104455 - the8472:dont-drain-on-drop, r=Amanieu
Browse files Browse the repository at this point in the history
Don't drain-on-drop in DrainFilter impls of various collections.

This removes drain-on-drop behavior from various unstable DrainFilter impls (not yet for HashSet/Map) because that behavior [is problematic](rust-lang/rust#43244 (comment)) (because it can lead to panic-in-drop when user closures panic) and may become forbidden if [this draft RFC passes](rust-lang/rfcs#3288).

closes #101122

[ACP](rust-lang/libs-team#136)

affected tracking issues
* #43244
* #70530
* #59618

Related hashbrown update: rust-lang/hashbrown#374
  • Loading branch information
bors committed Jun 15, 2023
2 parents 599ca56 + 6acd7ee commit 83e727b
Show file tree
Hide file tree
Showing 20 changed files with 399 additions and 534 deletions.
12 changes: 6 additions & 6 deletions alloc/benches/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,15 +385,15 @@ pub fn clone_slim_100_and_clear(b: &mut Bencher) {
#[bench]
pub fn clone_slim_100_and_drain_all(b: &mut Bencher) {
let src = slim_map(100);
b.iter(|| src.clone().drain_filter(|_, _| true).count())
b.iter(|| src.clone().extract_if(|_, _| true).count())
}

#[bench]
pub fn clone_slim_100_and_drain_half(b: &mut Bencher) {
let src = slim_map(100);
b.iter(|| {
let mut map = src.clone();
assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 100 / 2);
assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 100 / 2);
assert_eq!(map.len(), 100 / 2);
})
}
Expand Down Expand Up @@ -456,15 +456,15 @@ pub fn clone_slim_10k_and_clear(b: &mut Bencher) {
#[bench]
pub fn clone_slim_10k_and_drain_all(b: &mut Bencher) {
let src = slim_map(10_000);
b.iter(|| src.clone().drain_filter(|_, _| true).count())
b.iter(|| src.clone().extract_if(|_, _| true).count())
}

#[bench]
pub fn clone_slim_10k_and_drain_half(b: &mut Bencher) {
let src = slim_map(10_000);
b.iter(|| {
let mut map = src.clone();
assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 10_000 / 2);
assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 10_000 / 2);
assert_eq!(map.len(), 10_000 / 2);
})
}
Expand Down Expand Up @@ -527,15 +527,15 @@ pub fn clone_fat_val_100_and_clear(b: &mut Bencher) {
#[bench]
pub fn clone_fat_val_100_and_drain_all(b: &mut Bencher) {
let src = fat_val_map(100);
b.iter(|| src.clone().drain_filter(|_, _| true).count())
b.iter(|| src.clone().extract_if(|_, _| true).count())
}

#[bench]
pub fn clone_fat_val_100_and_drain_half(b: &mut Bencher) {
let src = fat_val_map(100);
b.iter(|| {
let mut map = src.clone();
assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 100 / 2);
assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 100 / 2);
assert_eq!(map.len(), 100 / 2);
})
}
Expand Down
8 changes: 4 additions & 4 deletions alloc/benches/btree/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ pub fn clone_100_and_clear(b: &mut Bencher) {
#[bench]
pub fn clone_100_and_drain_all(b: &mut Bencher) {
let src = slim_set(100);
b.iter(|| src.clone().drain_filter(|_| true).count())
b.iter(|| src.clone().extract_if(|_| true).count())
}

#[bench]
pub fn clone_100_and_drain_half(b: &mut Bencher) {
let src = slim_set(100);
b.iter(|| {
let mut set = src.clone();
assert_eq!(set.drain_filter(|i| i % 2 == 0).count(), 100 / 2);
assert_eq!(set.extract_if(|i| i % 2 == 0).count(), 100 / 2);
assert_eq!(set.len(), 100 / 2);
})
}
Expand Down Expand Up @@ -140,15 +140,15 @@ pub fn clone_10k_and_clear(b: &mut Bencher) {
#[bench]
pub fn clone_10k_and_drain_all(b: &mut Bencher) {
let src = slim_set(10_000);
b.iter(|| src.clone().drain_filter(|_| true).count())
b.iter(|| src.clone().extract_if(|_| true).count())
}

#[bench]
pub fn clone_10k_and_drain_half(b: &mut Bencher) {
let src = slim_set(10_000);
b.iter(|| {
let mut set = src.clone();
assert_eq!(set.drain_filter(|i| i % 2 == 0).count(), 10_000 / 2);
assert_eq!(set.extract_if(|i| i % 2 == 0).count(), 10_000 / 2);
assert_eq!(set.len(), 10_000 / 2);
})
}
Expand Down
2 changes: 1 addition & 1 deletion alloc/benches/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Disabling on android for the time being
// See https://github.com/rust-lang/rust/issues/73535#event-3477699747
#![cfg(not(target_os = "android"))]
#![feature(btree_drain_filter)]
#![feature(btree_extract_if)]
#![feature(iter_next_chunk)]
#![feature(repr_simd)]
#![feature(slice_partition_dedup)]
Expand Down
76 changes: 32 additions & 44 deletions alloc/src/collections/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ impl<K, V, A: Allocator + Clone> BTreeMap<K, V, A> {
K: Ord,
F: FnMut(&K, &mut V) -> bool,
{
self.drain_filter(|k, v| !f(k, v));
self.extract_if(|k, v| !f(k, v)).for_each(drop);
}

/// Moves all elements from `other` into `self`, leaving `other` empty.
Expand Down Expand Up @@ -1395,48 +1395,45 @@ impl<K, V, A: Allocator + Clone> BTreeMap<K, V, A> {
/// The iterator also lets you mutate the value of each element in the
/// closure, regardless of whether you choose to keep or remove it.
///
/// If the iterator is only partially consumed or not consumed at all, each
/// of the remaining elements is still subjected to the closure, which may
/// change its value and, by returning `true`, have the element removed and
/// dropped.
/// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
/// or the iteration short-circuits, then the remaining elements will be retained.
/// Use [`retain`] with a negated predicate if you do not need the returned iterator.
///
/// It is unspecified how many more elements will be subjected to the
/// closure if a panic occurs in the closure, or a panic occurs while
/// dropping an element, or if the `DrainFilter` value is leaked.
/// [`retain`]: BTreeMap::retain
///
/// # Examples
///
/// Splitting a map into even and odd keys, reusing the original map:
///
/// ```
/// #![feature(btree_drain_filter)]
/// #![feature(btree_extract_if)]
/// use std::collections::BTreeMap;
///
/// let mut map: BTreeMap<i32, i32> = (0..8).map(|x| (x, x)).collect();
/// let evens: BTreeMap<_, _> = map.drain_filter(|k, _v| k % 2 == 0).collect();
/// let evens: BTreeMap<_, _> = map.extract_if(|k, _v| k % 2 == 0).collect();
/// let odds = map;
/// assert_eq!(evens.keys().copied().collect::<Vec<_>>(), [0, 2, 4, 6]);
/// assert_eq!(odds.keys().copied().collect::<Vec<_>>(), [1, 3, 5, 7]);
/// ```
#[unstable(feature = "btree_drain_filter", issue = "70530")]
pub fn drain_filter<F>(&mut self, pred: F) -> DrainFilter<'_, K, V, F, A>
#[unstable(feature = "btree_extract_if", issue = "70530")]
pub fn extract_if<F>(&mut self, pred: F) -> ExtractIf<'_, K, V, F, A>
where
K: Ord,
F: FnMut(&K, &mut V) -> bool,
{
let (inner, alloc) = self.drain_filter_inner();
DrainFilter { pred, inner, alloc }
let (inner, alloc) = self.extract_if_inner();
ExtractIf { pred, inner, alloc }
}

pub(super) fn drain_filter_inner(&mut self) -> (DrainFilterInner<'_, K, V>, A)
pub(super) fn extract_if_inner(&mut self) -> (ExtractIfInner<'_, K, V>, A)
where
K: Ord,
{
if let Some(root) = self.root.as_mut() {
let (root, dormant_root) = DormantMutRef::new(root);
let front = root.borrow_mut().first_leaf_edge();
(
DrainFilterInner {
ExtractIfInner {
length: &mut self.length,
dormant_root: Some(dormant_root),
cur_leaf_edge: Some(front),
Expand All @@ -1445,7 +1442,7 @@ impl<K, V, A: Allocator + Clone> BTreeMap<K, V, A> {
)
} else {
(
DrainFilterInner {
ExtractIfInner {
length: &mut self.length,
dormant_root: None,
cur_leaf_edge: None,
Expand Down Expand Up @@ -1899,9 +1896,10 @@ impl<K, V> Default for Values<'_, K, V> {
}
}

/// An iterator produced by calling `drain_filter` on BTreeMap.
#[unstable(feature = "btree_drain_filter", issue = "70530")]
pub struct DrainFilter<
/// An iterator produced by calling `extract_if` on BTreeMap.
#[unstable(feature = "btree_extract_if", issue = "70530")]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ExtractIf<
'a,
K,
V,
Expand All @@ -1911,13 +1909,13 @@ pub struct DrainFilter<
F: 'a + FnMut(&K, &mut V) -> bool,
{
pred: F,
inner: DrainFilterInner<'a, K, V>,
inner: ExtractIfInner<'a, K, V>,
/// The BTreeMap will outlive this IntoIter so we don't care about drop order for `alloc`.
alloc: A,
}
/// Most of the implementation of DrainFilter are generic over the type
/// of the predicate, thus also serving for BTreeSet::DrainFilter.
pub(super) struct DrainFilterInner<'a, K, V> {
/// Most of the implementation of ExtractIf are generic over the type
/// of the predicate, thus also serving for BTreeSet::ExtractIf.
pub(super) struct ExtractIfInner<'a, K, V> {
/// Reference to the length field in the borrowed map, updated live.
length: &'a mut usize,
/// Buried reference to the root field in the borrowed map.
Expand All @@ -1929,30 +1927,20 @@ pub(super) struct DrainFilterInner<'a, K, V> {
cur_leaf_edge: Option<Handle<NodeRef<marker::Mut<'a>, K, V, marker::Leaf>, marker::Edge>>,
}

#[unstable(feature = "btree_drain_filter", issue = "70530")]
impl<K, V, F, A: Allocator + Clone> Drop for DrainFilter<'_, K, V, F, A>
where
F: FnMut(&K, &mut V) -> bool,
{
fn drop(&mut self) {
self.for_each(drop);
}
}

#[unstable(feature = "btree_drain_filter", issue = "70530")]
impl<K, V, F> fmt::Debug for DrainFilter<'_, K, V, F>
#[unstable(feature = "btree_extract_if", issue = "70530")]
impl<K, V, F> fmt::Debug for ExtractIf<'_, K, V, F>
where
K: fmt::Debug,
V: fmt::Debug,
F: FnMut(&K, &mut V) -> bool,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("DrainFilter").field(&self.inner.peek()).finish()
f.debug_tuple("ExtractIf").field(&self.inner.peek()).finish()
}
}

#[unstable(feature = "btree_drain_filter", issue = "70530")]
impl<K, V, F, A: Allocator + Clone> Iterator for DrainFilter<'_, K, V, F, A>
#[unstable(feature = "btree_extract_if", issue = "70530")]
impl<K, V, F, A: Allocator + Clone> Iterator for ExtractIf<'_, K, V, F, A>
where
F: FnMut(&K, &mut V) -> bool,
{
Expand All @@ -1967,14 +1955,14 @@ where
}
}

impl<'a, K, V> DrainFilterInner<'a, K, V> {
impl<'a, K, V> ExtractIfInner<'a, K, V> {
/// Allow Debug implementations to predict the next element.
pub(super) fn peek(&self) -> Option<(&K, &V)> {
let edge = self.cur_leaf_edge.as_ref()?;
edge.reborrow().next_kv().ok().map(Handle::into_kv)
}

/// Implementation of a typical `DrainFilter::next` method, given the predicate.
/// Implementation of a typical `ExtractIf::next` method, given the predicate.
pub(super) fn next<F, A: Allocator + Clone>(&mut self, pred: &mut F, alloc: A) -> Option<(K, V)>
where
F: FnMut(&K, &mut V) -> bool,
Expand All @@ -2001,7 +1989,7 @@ impl<'a, K, V> DrainFilterInner<'a, K, V> {
None
}

/// Implementation of a typical `DrainFilter::size_hint` method.
/// Implementation of a typical `ExtractIf::size_hint` method.
pub(super) fn size_hint(&self) -> (usize, Option<usize>) {
// In most of the btree iterators, `self.length` is the number of elements
// yet to be visited. Here, it includes elements that were visited and that
Expand All @@ -2011,8 +1999,8 @@ impl<'a, K, V> DrainFilterInner<'a, K, V> {
}
}

#[unstable(feature = "btree_drain_filter", issue = "70530")]
impl<K, V, F> FusedIterator for DrainFilter<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {}
#[unstable(feature = "btree_extract_if", issue = "70530")]
impl<K, V, F> FusedIterator for ExtractIf<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {}

#[stable(feature = "btree_range", since = "1.17.0")]
impl<'a, K, V> Iterator for Range<'a, K, V> {
Expand Down
Loading

0 comments on commit 83e727b

Please sign in to comment.