-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Tracking issue for Vec::extract_if and LinkedList::extract_if #43244
Comments
Add Vec::drain_filter This implements the API proposed in #43244. So I spent like half a day figuring out how to implement this in some awesome super-optimized unsafe way, which had me very confident this was worth putting into the stdlib. Then I looked at the impl for `retain`, and was like "oh dang". I compared the two and they basically ended up being the same speed. And the `retain` impl probably translates to DoubleEndedIter a lot more cleanly if we ever want that. So now I'm not totally confident this needs to go in the stdlib, but I've got two implementations and an amazingly robust test suite, so I figured I might as well toss it over the fence for discussion.
Maybe this doesn't need to include the kitchen sink, but it could have a range parameter, so that it's like a superset of drain. Any drawbacks to that? I guess adding bounds checking for the range is a drawback, it's another thing that can panic. But drain_filter(.., f) can not. |
Is there any chance this will stabilize in some form in the not to far future? |
If the compiler is clever enough to eliminate the bounds checks ( And I'm pretty sure you can implement it in a way |
I know this is bikeshedding to some extent, but what was the reasoning behind naming this |
No idea, but |
|
I think
|
There is no precedent for using |
The "said equivalent" code in the comment is not correct... you have to minus one from i at the "your code here" site, or bad things happens. |
IMO it's not Again, just from a newbie perspective, the things I would search for if trying to find something to do what this issue proposes would be I actually searched for It seems like a simple function named |
On a separate note, I don't feel as though this should mutate the vector it's called on. It prevents chaining. In an ideal scenario one would want to be able to do something like: vec![
"",
"something",
a_variable,
function_call(),
"etc",
]
.reject(|i| { i.is_empty() })
.join("/") With the current implementation, what it would be joining on would be the rejected values. I'd like to see both an |
You can already do the chaining thing with |
Yes, it's a member of |
Drain is novel terminology because it represented a fourth kind of ownership in Rust that only applies to containers, while also generally being a meaningless distinction in almost any other language (in the absence of move semantics, there is no need to combine iteration and removal into a single ""atomic"" operation). Although drain_filter moves the drain terminology into a space that other languages would care about (since avoiding backshifts is relevant in all languages). |
I came across
|
I still feel as though |
Shouldn't |
Yes |
Add Drop impl for linked_list::DrainFilter This is part of #43244. See #43244 (comment)
Maybe the API-description in the first post should be changed to use extract_if-terminology? As it is now, there's a note that the feature was previously known as drain_filter, but then the 'Public API'-section still talks about 'drain_filter'. For newcomers to this feature, I think it would reduce confusion! |
rust-lang/rfcs#3299 (comment) proposes a way to do that. |
Currently there is a bug if extract_if is combined with allocator api. impl<T, F, A: Allocator> Iterator for ExtractIf<'_, T, F, A>
where
F: FnMut(&mut T) -> bool,
{
type Item = T;
fn next(&mut self) -> Option<T> {
// ...
return Some(Box::from_raw(node.as_ptr()).element);
// ...
}
} It should be something like Box::from_raw_in(node.as_ptr(), &self.list.alloc) otherwise rust would deallocate it in Global. |
I hope this is the right place to discuss, but it'd be nice if this iterator did not store the predicate. Ideally I think the usage would be more like this: let mut drain_iter = vec.extract_iter();
while let Some(next) = drain_iter.next() {
if condition(next) {
// Calling `extract` on the iterator is what makes it different from `Iter`.
println!("extracted: {:?}", drain_iter.extract());
}
} The driving reason behind this suggestion is so that you can let mut drain_iter_a = vec_a.extract_iter();
let mut drain_iter_b = vec_b.extract_iter();
while let Some((next_a, next_b)) = drain_iter_a.next().zip(drain_iter_b.next()) {
if condition(next_a, next_b) {
println!("extracted: {:?} {:?}", drain_iter_a.extract(), drain_iter_b.extract());
}
} Doing this sort of thing otherwise is I've had multiple reasons for wanting this, but my current reason is that I want to be able to expose a slice of items from a container, and have the container also store metadata for each item (in a separate Here's a rust playground with a very bad implementation, but perhaps enough to play with the ergonomics. Bonus points if the method on |
I think having it work as above may also address several of the comments on #43244 (comment) - such as |
One significant downside to this approach would be the obstruction of building a pipeline, e.g. with let res: String = vec
.extract_if(confition)
.map(ToString::to_string)
.filter(|s| !s.is_empty())
.intersperse(", ".to_string())
.collect(); With your proposal, we;d either have to write it like it's 1999: let mut drain_iter = vec.extract_iter();
let mut first = true;
let mut res = String::new();
while let Some(next) = drain_iter.next() {
if condition(next) {
let s = drain_iter.extract().to_string();
if !s.is_empty() {
if first {
first = false;
res.push_str(", ");
}
res.push_str(s.as_ref());
}
}
} or basically hand-craft a new let mut drain_iter = vec.extract_iter();
let res: String = std::iter::from_fn(|| drain_iter.next(}.and_then(|i| if condition(i) {
Some(drain_iter.extract())
} else {
None
}))
.map(ToString::to_string)
.filter(|s| !s.is_empty())
.intersperse(", ".to_string())
.collect(); That being said, the existence of a |
Had an idea but now I think it only works for lending iterators. Another possibility would be for let mut drain_iter_a = vec_a.extract_iter();
let mut drain_iter_b = vec_b.extract_iter();
while let Some((next_a, next_b)) = drain_iter_a.next().zip(drain_iter_b.next()) {
if condition(&next_a, &next_b) {
println!(
"extracted: {:?} {:?}",
Extract::extract(drain_iter_a),
Extract::extract(drain_iter_b),
);
}
} but also let res: String = vec
.extract_iter()
.filter_map(|it| {
if condition(&it) {
Some(Extract::extract(it))
} else {
None
}
})
.map(ToString::to_string)
.filter(|s| !s.is_empty())
.intersperse(", ".to_string())
.collect(); |
I hadn't considered that, I agree.
I think this would be good. We could build one on top of the other, but we'd want to check whether the optimiser could give comparable performance to having seperate implementations. I've been messing around with the |
Sorry for all the extra discussion here, let me know if it's better to move it to another issue. Inspired by @jplatte's suggestion I've come up with an implementation that makes an The summary of this is that there's 3 types:
The pub trait ExtractingIterator: ExactSizeIterator {
type OwnedItem;
fn peek_mut(&mut self) -> Option<Self::Item>;
fn extract(&mut self) -> Self::OwnedItem;
} The implementation of See this gist for the details:
Granted you do not get combining multiple vectors out of the box, but I believe it's much more flexible this way, and you can construct the multi-vector way fairly simply without any complicated logic or unsafe code. |
|
I'm only here to say that I would prefer |
The documentation on extract_if's iterator drop behaviour mentions retaining the remaining elements, but it contradicts the drop impl, which just logically moves non-traversed elements without checking pred. Also I believe that the current name is unwarranted due to being essentially hard to find, containing if and having non-clear intention behind it either way. |
I would prefer And the documentation also says that using this method is equivalent to the following code: let mut i = 0;
while i < vec.len() {
if some_predicate(&mut vec[i]) {
let val = vec.remove(i);
// your code here
} else {
i += 1;
}
} |
From
The while predicate(...) {
// do job
}
// stop job But as you mention the equivalent code of |
This is a subtle difference. I prefer
|
I also understand this func in terms of "drain". I think this word should appear in its name somehow. |
drain/extract already conveys a continuous iteration. |
The methods were intentionally renamed because they behave differently compared to |
I wasn't aware of that, thanks. It makes sense to be different then. |
Maybe the history section should be updated to highlight the name change and the reason a bit better |
Is it possible to point to this feature when a user is trying to use the |
…lter` with `extract_if` rust-lang/rust#43244
I'm eagerly anticipating the addition of this feature to the standard library. Could it be stabilized this year? |
Feature gate:
#![feature(extract_if)]
(previouslydrain_filter
)This is a tracking issue for
Vec::extract_if
andLinkedList::extract_if
, which can be used for random deletes using iterators.Public API
Steps / History
Unresolved Questions
extract_if
accept aRange
argument?Send
+Sync
impls on linked list's ExtractIf, see commentSee #43244 (comment) for a more detailed summary of open issues.
The text was updated successfully, but these errors were encountered: