Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vec::retain could (should?) give &mut T to the closure, not just &T #25477

Closed
SimonSapin opened this issue May 16, 2015 · 25 comments
Closed

Vec::retain could (should?) give &mut T to the closure, not just &T #25477

SimonSapin opened this issue May 16, 2015 · 25 comments

Comments

@SimonSapin
Copy link
Contributor

Vec<T> has this method:

pub fn retain<F>(&mut self, mut f: F) where F: FnMut(&T) -> bool

However, looking at its implementation, I don’t see a reason why the &T part couldn’t be &mut T, allowing in-place mutation of retained items. This would make the method more useful. The only downside that I can see is that the name does not reflect this new capability of the method. I believe this change would be backward-compatible.

This came up at today’s meetup where someone have a Vec<Item> and wanted (but didn’t know how) to iterate the vector, decreasing some quantity inside Item but also removing items from the vector when the quantity reaches zero.

CC @aturon

@bluss
Copy link
Member

bluss commented May 16, 2015

Speaking in context of rust-lang/rfcs#1105 this is a breaking change, but probably qualifies as a minor change. It might be interesting to pull it into the discussion as an example.

For the idea itself, it sounds sound and useful.

@SimonSapin
Copy link
Contributor Author

Oh, right. I was thinking of using this method with a closure with inferred parameter types, but it is indeed breaking if you have type annotations.

From the RFC:

it must always be possible to locally fix the problem through some kind of disambiguation that could have been done in advance (by using more explicit forms)

This case seems unusual since using the more implicit form is the change you could do in advance, but I suppose it qualifies.

@aturon
Copy link
Member

aturon commented May 28, 2015

I am +1 for this change in principle, but slightly worried about potential breakage. It's probably worth prototyping and testing out with @brson's crater tool.

@Amanieu
Copy link
Member

Amanieu commented Nov 6, 2015

If this causes breakage, an alternative would be to add a separate retain_mut method. The downside is that we then end up with two very similar methods, which might become confusing.

@Amanieu
Copy link
Member

Amanieu commented Nov 6, 2015

Changing the parameter to &mut breaks uses of retain with closures of the form |&x| {...}. There are a few uses of this in rustc, so this pattern might be used in user code as well.

One example in rustc:

../src/librustc/middle/infer/higher_ranked/mod.rs:445
region_vars.retain(|&region_vid| {

@Gankra
Copy link
Contributor

Gankra commented Nov 6, 2015

I'm not a huge fan of doing this. We made a bad call. That sucks, but this is also why we expose the guts of Vec.

@bluss
Copy link
Member

bluss commented Nov 7, 2015

It's interesting in general that we could choose to give out &mut instead of &, everywhere there is exclusive access even if you'd typically not mutate. Another example is Iterator::filter.

Sounds like the resolution shall be a separate API @gankro, retain_mut() which can be developed outside libstd.

@Gankra
Copy link
Contributor

Gankra commented Nov 7, 2015

Another case is BTree::iter_mut for the keys. I think in most of these cases, mutable access is a bit "strange" to have intuitively (and in the case of BTree, a footgun), but I think that's because we're not super used to thinking of &mut and & in terms of their shared-ness, but rather in terms of mutability (because that's often the most natural perspective IMO).

@bluss
Copy link
Member

bluss commented Nov 7, 2015

retain_mut is simple to lift out (retain uses only public api). I've put retain_mut into the crate odds.

I've suggested we have some kind of gathering of more-than-libstd collections API in a crate in contain-rs, no other interested people have said anything in that discussion yet.

@wthrowe
Copy link
Contributor

wthrowe commented Nov 7, 2015

Given that F: Fn(.., &T, ..) can be called anywhere that F: Fn(.., &mut T, ..) can, I wonder if it would be possible to make them coerce? Then the problems with this change would presumably be reduced to just type-inferal issues. (Obviously that would need an RFC.)

@Amanieu
Copy link
Member

Amanieu commented Nov 7, 2015

I wrote an RFC for retain_mut: rust-lang/rfcs/pull/1353

@arthurprs
Copy link
Contributor

I'm ok with this change.

@steveklabnik
Copy link
Member

Triage: as time goes on here, I don't think that we can make this change, as the breakage is much more significant than in the few days post 1.0. I'm going to give it a close.

@real-felix
Copy link

real-felix commented Sep 21, 2017

So what is planned to remove elements based on predicates and mutate the elements? #34265 has been closed in favor of this issue, but this issue has been closed too.

@bluss
Copy link
Member

bluss commented Sep 21, 2017

Drain filter is going to cover that.

@real-felix
Copy link

If I do understand, retain will be useless? Each call to retain can be replaced with a call to drain_filter.

@bluss
Copy link
Member

bluss commented Sep 22, 2017

Well, that's up to your judgement. Existing retain usages will continue to work, which has a value.

@shepmaster
Copy link
Member

retain will be useless?

Also, drain_filter is an iterator, so you have to drive it to completion for it to have any effect. retain is more eager, so it's more ergonomic for that case:

    let mut v = vec![1, 2, 3, 4, 5, 6];
    v.retain(|x| x % 2 == 0);
    println!("{:?}", v);

    let mut v = vec![1, 2, 3, 4, 5, 6];
    v.drain_filter(|x| *x % 2 != 0).count();
    println!("{:?}", v);

@bluss
Copy link
Member

bluss commented Sep 22, 2017

Existing drain_filter impl does not need to be driven explicitly -- it is driven in drop, too. It makes panic behaviour interesting but it is similar to .drain().

@shepmaster
Copy link
Member

it is driven in drop, too

Ah, I guess that makes sense.

@upsuper
Copy link
Contributor

upsuper commented Nov 28, 2018

FYI, I just published a crate retain_mut which implements the idea here.

@cheako
Copy link

cheako commented Jun 12, 2020

I've read all the comments, but still confused about what to do, that's not good! Ideally there would be a pinned comment or something with current best solution. I see that there are two crates odds and retain_mut that implement this... Are they best solution?

@Boscop
Copy link

Boscop commented Jun 13, 2020

@cheako You can also use drain_filter but you have to negate the closure's return value.

@cheako
Copy link

cheako commented Jun 13, 2020

@Boscop drain_filter does something different... or it's documentation isn't as clear as retain's. drain_filter creates an iterator that would then need to be consumed ??for anything to happen??
Looking into it, that just leads to another question... Not a solution that's easily grasped. If drain_filter was supposed to be retain_mut it's not clear how.

bors bot added a commit to RoaringBitmap/roaring-rs that referenced this issue Jan 23, 2021
85: Replace Vec remove by a retain_mut function in Container operations r=Kerollmops a=Kerollmops

We introduce a `retain_mut` function that allows us to mutate the iterated element. There already were [a discussion about this `Vec::retain` parameter restriction](rust-lang/rust#25477).

Using the `retain` algorithm is much better when multiple stores must be removed in one batch, same as in #83.

Co-authored-by: Clément Renault <[email protected]>
@Ten0
Copy link

Ten0 commented Jan 28, 2022

For reference, retain_mut is currently being added to the standard library: #90829

not-jan pushed a commit to not-jan/roaring-rs that referenced this issue Aug 31, 2022
85: Replace Vec remove by a retain_mut function in Container operations r=Kerollmops a=Kerollmops

We introduce a `retain_mut` function that allows us to mutate the iterated element. There already were [a discussion about this `Vec::retain` parameter restriction](rust-lang/rust#25477).

Using the `retain` algorithm is much better when multiple stores must be removed in one batch, same as in RoaringBitmap#83.

Co-authored-by: Clément Renault <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests