Implemented in https://github.com/rust-lang/rust/pull/40325
```rust
impl
pub fn remove_item(&mut self, item: &T) -> Option
}
PR moved to #40325 due to reasons
Comments from original thread:
VecDeque
too.remove_last_item
desired?@clarcharr for remove_last_item
you can simply do vec.remove(vec.len() - 1))
@madseagames I meant specifically a method that searches for the item from the end of the vec instead of the beginning
What do you think about adding remove_swap_item
? Just as remove_swap
reduces the runtime complexity from O(n) to O(1) compared to remove
, remove_swap_item
would reduce it from O(n + (n-m)) to O(n).
I like swap_remove_item
.
It would be nice if this was more generic.
let v: Vec
v.remove_item("s"); <-- fails because &str is not a &String
we really want the remove_item "item: &T" to be "something that we can compare equality with the type in the container". This would let remove_item() walk the T items, compare equality, and remove if equal.
Seems reasonable to change the signature to fn remove_item<U>(&mut self, item: &U) -> Option<T> where T: PartialEq<U> + ?Sized
Reviewing the naming used in method names and the docs, it would seem that iterators yield _items_ while vecs contain _elements_. Considering that we insert elements then we should also remove elements and the method should be called remove_element
. That name is a bit long so I'd propose simply remove_eq
. However I wonder if we should favor a more general remove_by
, or have both.
fn remove_by<F: FnMut(&T) -> bool>(&mut self, f: F)
Any plan to stabilize this?
This has been in nightly for a year with no issue reported on the method itself. There are proposals for additional methods, but that shouldn’t block this one. Please submit separate PRs or RFCs.
@rfcbot fcp merge
Team member @SimonSapin has proposed to merge this. The next step is review by the rest of the tagged team members:
Concerns:
Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
Are we switching to a V: PartialEq<T>
bound?
Oops, sorry I missed that. Yes, let’s.
Again here, as a maintainer of collections that offer the Vec and VecDeque APIs I'd like at least a summary comment explaining what this method does, why this design was followed, alternatives, and addressing the issues mentioned here that have received zero replies:
VecDeque
?Seems reasonable to change the signature to
fn remove_item<U>(&mut self, item: &U) -> Option<T> where T: PartialEq<U> + ?Sized
item
s instead of elements? why remove_items
instead of remove_by
, etc.@rfcbot concern rationale-and-vecdeque
Just gonna formally register @gnzlbg's concern above
Not an issue with the method per se, but it may need to be very clearly documented that the reference is not supposed to be to an item that is in the Vec
already, as it creates impossible borrow-checker conundrum.
Users coming from the C world of pointers but no proper equality comparison, may be confused by this method, e.g. https://users.rust-lang.org/t/difficult-borrowing-situation/16794
The remove_eq
name mentioned previously would help.
@SimonSapin as the proposer for FCP, do you have thoughts on @gnzlbg's concern?
My thinking is that the remove_by
suggested by @leodasvacas would sidestep most of the issues here.
cc @SimonSapin re. https://github.com/rust-lang/rust/issues/40062#issuecomment-389903127
I don’t have a strong desire for this particular feature myself, I proposed FCP as an effort to reduce the number of features in "unstable limbo". That said:
Regarding VecDeque
, sure, I don’t see a reason not to add it there as well.
remove_by
as proposed in https://github.com/rust-lang/rust/issues/40062#issuecomment-323403342 sounds good to me. It is more general, and goes with precedent of Iterator::position
also taking a boolean callable predicate rather than relying on PartialEq
. (Even further, the precedent of Iterator
not having a method like position
that would rely on PartialEq
.)
I notice two things:
I think we should not stabilize this just because it exists, but instead someone should come up with a well justified explanation of exactly what the best "find and remove" set of methods of vec-likes would be.
Good points.
drain
(which is stable) and drain_filter
https://github.com/rust-lang/rust/issues/43244 also fit the "find and remove" description, and we had a similar concern https://github.com/rust-lang/rust/issues/43244#issuecomment-376886108 about the combinations of filtering v.s. non-filtering and self-exhausting on drop (https://github.com/rust-lang/rfcs/pull/2369) v.s. not.
@rfcbot resolve rationale-and-vecdeque
I don't want to personally be on the hook for blocking this any more
:bell: This is now entering its final comment period, as per the review above. :bell:
The final comment period, with a disposition to merge, as per the review above, is now complete.
As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.
The RFC will be merged soon.
I still think this should be called remove_eq
instead of remove_item
.
My problem with that name is that it only removes the first matching item, not all that are eq
(though I haven't read the relevant RFC to know whether the name has been hashed out already; I assume it has been).
I don't think the naming issue has been resolved. The implementation is still using the confusing remove_item
name.
@mathstuf remove_first_eq
then?
@rust-lang/libs Can someone clarify what the status of this is wrt. the conversation above?
I notice two things:
1. There are a lot of methods similar to this we could add, and its not obvious to me which set of those methods is the ideal set. 2. This has sat in FCP concern limbo for 8 months without anyone complaining.
I think we should not stabilize this just because it exists, but instead someone should come up with a well justified explanation of exactly what the best "find and remove" set of methods of vec-likes would be.
An example: I'm writing a proc macro attribute, and amongst the tokens, I expect other attribute to be there. This would be useful to add:
if let Some(attr) = attrs.remove_item(|item| attr.path.is_ident("foo")) {
// Handle the `foo` attribute case...
}
@Boiethios Until we sort this out, you can use attrs.iter().position(|item| some_boolean(item)).map(|i| attrs.remove(i))
Ping. What's the status of this? Could we stabilize something?
@mathstuf remove_first
? Given we already have drain_filter for accepting a predicate, it would be nice to have a method that removed a single object from the vector (if there). Removing something from a vec is exceedingly common code. Getting the position and then removing that position is longwinded for such a common operation.
(Btw, I think _first
bit of naming is excellent as it highlights that there may be others still in the vec - a method name that prompts one to think is a good thing.)
I use retain
for this, and haven't found it too cumbersome. It differs in that it removes all copies of a given item, but is very easy for many purposes.
Retain everything but the object? As work-arounds go that's shorter, but it's not ideal for expressing intent. Still keen on remove_first
and swap_remove_first
.
Just to bring up a different opinion: how about removing this method instead of stabilizing it? While yes, it would be convenient to have, it might not be worth it.
For one, remove_item
is not a good name, as many already said. The name doesn't imply that the first item from the start is removed. remove_first
is better in that regard, but still doesn't feel completely right.
More importantly, if we have remove_first
, we should certainly also have remove_last
. Having one but not the other feels arbitrary to me and makes for a surprising API (that's bad). Similarly, we would also want to add the swap_remove
variant of both of those methods. Potentially, we also want to add a variant that removes all items equal to the given needle
. And as also already mentioned, we can also add all those methods to VecDeque
. And why not LinkedList
(without the swap_remove
versions)? Consequently, we would have to add 10 or 15 new methods. Adding fewer would make the API inconsistent IMO.
All the functionality can be implemented with very little extra code with iterators.
@LukasKalbertodt How do you propose this be implemented with iterators?
How about this signature:
impl<T: PartialEq> Vec<T> {
fn remove_eq(&mut self, item: &T, limit: Option<usize>) -> impl Iterator<Item = T>;
}
This uses remove_eq
to make it clear exactly what is removed. The limit
argument and the return type makes it clear that we remove many items. The user has the option of how many to remove (None
means all). The returned iterator can be implemented efficiently by shifting the items and then pulling a similar trick (using Vec::set_len
) to Drain
to yield the removed items.
How do you propose this be implemented with iterators?
@SimonSapin already mentioned this above:
v.iter().position(|&n| n == 2).map(|i| v.remove(i))
How about this signature:
I think that one is too complex. Especially when considering the call site, I think it's fairly hard to understand. And the name still does not imply that the search begins at the beginning, which is a big nono for me.
Removing an item from a vec is a _very_ common codepath. Somehow it needs
to be a one liner. Having swap_remove_first() and not having remove_first()
would be nudging people in the direction of great performance. That seems
quite rustic to me.
swap_remove_last is interesting as there maybe no swap needed if it happens
to be the last item - cute.
Just have those two functions. That’s the 80% case covered. People who wish
to maintain order will just have to have slightly more code.
On Fri, 15 Nov 2019 at 19:20, Lukas Kalbertodt notifications@github.com
wrote:
How do you propose this be implemented with iterators?
@SimonSapin https://github.com/SimonSapin already mentioned this above
https://github.com/rust-lang/rust/issues/40062#issuecomment-480060761:v.iter().position(|&n| n == 2).map(|i| v.remove(i))
How about this signature:
I think that one is too complex. Especially when considering the call
site, I think it's fairly hard to understand. And the name still does not
imply that the search begins at the beginning, which is a big nono for me.—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/40062?email_source=notifications&email_token=AAGEJCHYNLO4ZKAMVMA5BOTQT3Y6RA5CNFSM4DBIVPY2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEGOGDA#issuecomment-554492684,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAGEJCA4CI3ECYCCXJLCDODQT3Y6RANCNFSM4DBIVPYQ
.
I don't understand the focus on removing here. Vec has a lot more members taking an index than remove
and swap_remove
, and only a few to get hold of an index value (binary_search
variants and len
, sort of). Most of the complication in the "workaround" for this remove_item is finding the first index of something:
v.iter().position(|&n| n == 2)
Not too bad, the iter
reminds you it's a linear search, but could be easier. Worse for the last index:
v.iter().rev().position(|&x| x == 2).map(|i| v.len() - i - 1)
Contrast to the find
and rfind
in that famous vector called string that infiltrated Rust (ugly names but at least they convey that a search is involved).
I've vary rarely needed this, I don't advocate making linear searches easy, it just seems more useful to try to make searching easier, before making search-and-removing easier.
@ssomers rposition
already exists (so does rfind
). And even if it didn't exist, that has no relevance to whether remove_item
should exist or not, they're separate features, so they can both exist, it isn't either/or.
(I personally have had many cases where remove_item
would have been useful, it's a common operation in any language.)
Waaait! It's getting stabilized without fixing the confusing name!
Please please don't call it remove_item
, as this is the same name as in other languages that can remove item from an array by presenting an item from the same array. Rust's version can't do that due to borrow checker and it's a trap for users.
I absolutely wouldn't call it a trap.
It seems like many people agree that the name should at least be up for debate again. Really unfortunate how the name discussion was somehow forgotten during FCP. So I guess we should revert stabilization as soon as possible so it won't end up in beta?
So I guess we should revert stabilization as soon as possible so it won't end up in beta?
Sounds reasonable. Maybe someone wants to volunteer to lead the process about settling on a name for this ?
The next release is scheduled for Jan 30th. So we have about 3 weeks to figure out a new name.
I went through the thread again and it seems like the following names are suggestions for the method as it currently exists:
remove_element
(iterators have "items", Vec has "elements")remove_eq
remove_first_eq
remove_first
Personally, I would absolutely include first
, as that's an important information and it makes the user think. I have no preference between remove_first
and remove_first_eq
.
However, from reading the thread again it seems there are plenty of other concerns and suggestions that haven't been resolved yet:
fn remove_by<F: FnMut(&T) -> bool>(&mut self, f: F)
(although I don't understand that one. Is it just a retain
with flipped predicate? In that case I don't think it's useful. If it just removes the first element, then first
absolutely needs to be in the name IMO.)swap_remove_*
versions to nudge people to better performance@withoutboats mentioned here:
I think we should not stabilize this just because it exists, but instead someone should come up with a well justified explanation of exactly what the best "find and remove" set of methods of vec-likes would be.
@SimonSapin also raised some more concerns here, and I raised some concerns here.
I don't see where these concerns have been resolved. From re-reading this whole discussion, it isn't clear to me what the goal of adding this method is nor what problem does this method solve or whether this is a problem worth solving. What's more or less clear to me is that there is at least some design work to be done to at least ensure that libstd exposes a consistent API, and that sounds like this would warrant an RFC to me. The current discussion about using a _by
method hints that these APIs might not only want to be consistent for the ordered collections (Vec, VecDeque, List) for which a notion of a "first" element makes sense, but might also want to be consistent with the e.g. _by
and _by_key
methods of slices.
The main arguments for FCPing this appear to be that this has sit on nightly for too long without anybody complaining, and the main argument for stabilizing this has been that this has been in FCP for too long. I think it would be better to just encourage and support those that want to land this feature to write a small RFC for it. If somebody is interested in doing that, I would be able to help and give feedback.
Unless a concern is made with @rfcbot concern
by a relevant team member, then it won't stop FCP. And it won't go into FCP unless the majority of team members have voted for it (and there aren't any formal concerns listed).
So the fact it passed FCP means that all relevant team members explicitly and intentionally marked that they were okay with the current design and did not have any outstanding concerns at the time. Things do not go into FCP simply because it's been "sitting around too long".
Of course if any team members do have any concerns, now would be the best time to make them.
@Pauan
So the fact it passed FCP means that all relevant team members explicitly and intentionally marked that they were okay with the current design and did not have any outstanding concerns at the time.
Mistakes happen. And to me this thread clearly looks like many concerns were overlooked accidentally and no one explicitly marked them as concerns for rfcbot. Luckily those mistakes were caught.
Reopening because #68089
The current signature is missing a ?Sized bound. Could I ask for a PR to fix the bound and add a test that covers remove_item("...") on a Vec
#![feature(vec_remove_item)]
fn main() {
let mut v = vec![String::new()];
v.remove_item("");
println!("{:?}", v);
}
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:5:19
|
5 | v.remove_item("");
| ^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `str`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
Separately: I am leaning toward removing remove_item. I agree with https://github.com/rust-lang/rust/issues/40062#issuecomment-554486688 and https://github.com/rust-lang/rust/issues/40062#issuecomment-560309989. The currently design involves:
To generalize almost all of the discussion above, people are questioning whether the conjunction of all 5 of these decisions is really as common as a name like remove_item would suggest, whether a different permutation of choices might be more widely applicable, and how to adjust naming to leave room for all the other equally or more common permutations:
Fortunately Vec already supports all permutations of these use cases equally well, with nice independent APIs for searching and removing.
Many people have voiced opinions equivalent to "removing an item from a vec is so common that there absolutely must be a function for it". I think what they mean is that the union of all permutations of the above 5 choices is common, which is true; they don't necessarily mean the specific choices that go into remove_item as it exists would justify a method specific to those choices. https://github.com/rust-lang/rust/issues/40062#issuecomment-554486688 points out that satisfying the set of use cases which all together are common would take a much larger number of new methods.
Per https://github.com/rust-lang/rust/issues/40062#issuecomment-583911228, I think we should go ahead and remove Vec::remove_item
.
@rfcbot fcp close
@dtolnay Many people have voiced opinions equivalent to "removing an item from a vec is so common that there absolutely must be a function for it". I think what they mean is that the union of all permutations of the above 5 choices is common
That is not what I meant. When I said that I need remove_item
in my code, I meant exactly that: that I could take my existing code and replace it with remove_item
and it would have exactly the same behavior, because I really do need remove_item
specifically, not the other fancier methods.
I'm perfectly fine with changing the name so that we can accommodate other fancier methods in the future, but why would that mean that we shouldn't have remove_item
at all?
@rfcbot fcp cancel
@dtolnay proposal cancelled.
@rust-lang/libs: Per https://github.com/rust-lang/rust/issues/40062#issuecomment-583911228, I think we should go ahead and remove Vec::remove_item
.
@rfcbot fcp close
Team member @dtolnay has proposed to close this. The next step is review by the rest of the tagged team members:
No concerns currently listed.
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
Checking KodrAus’ checkbox per https://github.com/rust-lang/team/commit/45013d114d89480c33cab2a1a99828e4aacb8aee
:bell: This is now entering its final comment period, as per the review above. :bell:
The final comment period, with a disposition to close, as per the review above, is now complete.
As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.
Since we've merged the deprecation would we like to close this tracking issue now? Or keep it open until the unstable deprecated method is removed entirely?
I think keeping this open until we removed the method is a good idea. How long do we want to wait with that by the way? I guess a couple of cycles is sufficient?
Before closing, it would be great if someone could suggest a "one clean and obvious" way to remove the first/last occurance of an item from a Vector.
Irrespective of the arguments made here, a method removing the first/last occurance of an item is provided across all major programming languages, and is to be "expected"
Anyone new to rust is bound to google "remove from vec rust" and end up on this discussion which would only leave them more confused.
This StackOverflow Q&A should answer those questions. But here is a copy of my answer for convenience:
needle
// Panic if no such element is found
vec.remove(vec.iter().position(|x| *x == needle).expect("needle not found"));
// Ignore if no such element is found
if let Some(pos) = vec.iter().position(|x| *x == needle) {
vec.remove(pos);
}
You can of course handle the None
case however you like (panic and ignoring are not the only possibilities).
needle
Like the first element, but replace position
with rposition
.
needle
vec.retain(|x| *x != needle);
swap_remove
Remember that remove
has a runtime of O(n) as all elements after the index need to be shifted. Vec::swap_remove
has a runtime of O(1) as it swaps the to-be-removed element with the last one. If the order of elements is not important in your case, use swap_remove
instead of remove
!
It might be worth adding more examples (maybe all of the ones above) to the Vec::remove
docs.
I'd like to add those to the docs, should they go along the example that we currently have?
@nrabulinski What examples are you referring to? I could imagine those "remove examples" either going on the type (as a new section "Removing elements" or something like that) or directly on the Vec::remove
method (below the existing docs). And by the way, feel free to add (parts of) my text/examples verbatim.
Thank you. I was referring to those "remove examples" as to whether I should append them to what we currently have in docs for Vec::remove
, put them somewhere else or perhaps modify examples for Vec::remove
to include removal of specific element
append them to what we currently have in docs for
Vec::remove
That sounds fine to me. Feel free to just open a PR and we can still see about the specifics then :)
Most helpful comment
Any plan to stabilize this?