-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add filter_in_place to Vec and VecDeque #1353
Conversation
The RFC alludes to this, but not everyone may be familiar with the details, so I'd just like to note that the "mutate and then remove" pattern often implies that you're using an array not as an ordered data store, but rather as simply an efficient container with no particular ordering constraints. If this is the case, the following may serve one's usecase: // Pretend these are more complicated Enemy structs with an `hp` field
let mut enemies = vec![1, 3, 2, 1, 4];
// Iterate over the array backwards *by index*
for i in (0 .. enemies.len()).rev() {
// Process the enemy, determine if it should be removed
let should_remove = {
// Everyone take some damage! Remove the dead!
let enemy = &mut enemies[i];
*enemy -= 1;
*enemy == 0
}
if should_remove {
// Swap this enemy with the end of the array, and then
// pop it off. This "scrambles" the array, but that's
// ok because we don't care about order. Also, the only
// elements that are scrambled are the ones we've already
// seen, so this won't cause us to accidentally skip or
// reprocess an enemy. Further, the fact that the `len` of
// the array is decreased by this op doesn't matter to us,
// because we're about to go to a smaller index.
enemies.swap_remove(i);
}
} In principle, retain_mut can probably be more efficient, I think. That's not to say this is particularly inefficient, though. I also wouldn't expect tons of people to know this trick -- I only know it because this is a standard gamedev problem. It's also relying on the fact that we can proxy array iteration with indices, so it doesn't work on e.g. BTree or HashMap, which presumably also want retain. On that note, would BTree and HashMap provide retain and retain_mut, or would they "learn from Vec's mistake" and only provide retain_mut (possibly just called retain)? Finally, there's a moonshot alternative here: |
For my use case I actually do rely on the fact that As for adding |
🔔 This RFC is now entering its week-long final comment period 🔔 |
My own personal feelings on this RFC is that it seems like a somewhat niche use case that we may not want to pursue adding to the standard library at this time. I haven't personally come across this use case much, but I would certainly believe it exists though! |
vec.retain_mut(|&mut x| { | ||
x -= 1; | ||
x != 0 | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example won't work as described. The &mut x
pattern will copy x
out of the vector and then mutate a stack-local x
(which might not even compile because I don't think this x
binding is mutable). The example should be written thusly:
vec.retain_mut(|x| {
*x -= 1;
*x != 0
});
It seems like a strictly-better |
I wrote a similar function for a program where I needed a lot of in-place mapping and filtering, but I wanted to move items to an other vector instead of discarding them. The program required this to be done repeatedly, as fast as possible, so fn retain_map<F>(&mut self, mut f: F) where F: FnMut(T) -> Option<T> ...where the closure takes ownership of each item and returns it if it should stay in the vector, similar to |
That looks like it might change performance, because the items are moved around. |
Yes, I don't think it should be a replacement for |
Just to toss it out there, here is the example I run into a lot: I have a
except It would be nice if Ogeon's suggestion actually optimized out all the moves. I've definitely wanted a Edit: sorry, of course it moves, because |
My 2 cents.
More generic interface could be iterator-like:
So function that removes equal elements may look like this:
|
Actually,
It is a minimal interface which can be used to implement I'm not sure it is over-engineering, but it least it is simple and generic. |
@stepancheg Yet, |
The libs team discussed this during triage today and due to the recent discussion that has happened we're going to re-up the final comment period for another week. @aturon also brought up that we may be able to just find a new name for the |
It might also be worth trying to change the signature directly and doing a crater run, though I suspect there's non-trivial breakage. |
As I mentioned in the RFC, this will break any closure that uses |
Just had another use case for @gankro's version with iterating over indices and using |
You can implement |
The libs team discussed this RFC during triage yesterday and the conclusion was that we're going to move this out of FCP for now, there's not quite a sense of consensus just yet. There's a few alternatives we were discussion which seemed like the front-runners for what can happen here.
The downside of (1) is that it's an extra method, with (2) we're breaking everyone's muscle memory they have today, and (3) is unfortunately a little vague still on the details as to how we may wish to implement it. For pursuing (3) it's probably best to prototype on crates.io first so we have a concrete implementation to talk about and look at. Overall it didn't seem like there was a great sense of urgency in landing this RFC to get this functionality somehow, so we thought that moving this out of FCP for some more discussion was the best course of action for now. |
@Amanieu do you have thoughts on my previous comment about tweaking this RFC? The libs team was discussing this as a candidate for FCP yesterday but it unfortunately hasn't moved much since that last comment :( |
Well I don't have any particular preference for any of them, in the end they all bring in the same functionality. However I think adding a new iterator type just for this adds too much complexity for what is a rather niche use case. |
I personally feel the same way as well, what do you think of the |
I feel that |
…nd deprecate retain
I changed the RFC to add 2 new functions: |
@alexcrichton @Amanieu Deprecating a perfectly fine method just because it is superseded is not something I agree with. Deprecate stable API when it is actively harmful or confusing to users. |
To me both retain and this RFC fall short. Namely, it is not unusual for index to have some semantic value (consider, What I think is that Yet another thing that wasn’t voiced here, is that |
An advanced version of drain that uses a retain like filter would be interesting. |
From a comment by wthrowe:
That was actually my first thought when I changed I'd like to see that section of the Alternatives expanded. If we can make that change, then we can "easily" change |
been recently bitten by the fact that the index was not provided by the retain So for me, adding a fn retain_indexed_mut<F>(&mut self, f: F)
where F: FnMut(usize, &mut T) -> bool. It's currently possible to do it manually – by remembering the index outside the closure, but that requires assumption that Speaking of the "two loops" workaround for lack of @Ogeon The |
...assuming that the type implements Anyway, I didn't say that it was a better alternative, than anything else. Just another one. |
@Amanieu this RFC seems to have unfortunately stalled, and the @rust-lang/libs team's previous conclusions seem to not be reflected in the most recent update? Is there a strong reason for not taking any of those routes and going for these two functions instead? |
My personal opinion is to just close this RFC since it seems to be too controversial. |
Since there are many different counter-proposals here, no consensus, and no movement, closing per @Amanieu. Desirable feature, but back to the drawing board. |
For what it's worth, I was really surprised today that there's no retain_mut. Lots of other methods on Vec have 'mut'-version, but not retain. Seems like a hole. Not a biggie, since it's obviously easy to implement it yourself. But still! Wish this hadn't stalled! :-) To others coming here after searching for 'retain_mut': There seems to be a nightly function "drain_filter" which can be used, as well as the retain_mut crate with a trait implemented for Vec which adds a retain_mut to Vec. So lots of options and no need to get stuck! :-) |
Rendered