-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Remove finished
flag from MapWhile
#68820
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
Remove finished
flag from MapWhile
#68820
Conversation
I don't think we should do this. While it is true that However, I am not the libs team (nor on it), so let's r? @LukasKalbertodt I would suggest a fcp to close this, personally. |
I was also afraid of this change. However, later I came with than it simplifies code by removing behaviour that isn't required (nor needed) by I think this change won't bring a lot of cognitive problems, mostly because in 99% of places where you can use iterator (like The addition to docs is there just to be sure that nobody will ever have problems with this. But anyway the decision is up to the libs team. Personally I think that is approach is more correct. |
Actually, thinking about this more... Unless I'm missing something. Edit: No, this is wrong, thinking further. |
Oh, no, so then I think doing this makes sense after thinking about it (much) more. However, we should drop the _while suffix and instead call this Essentially, an Iterator is a stream of |
The implementation is indeed exactly like It's unfortunate we can't remove |
@Mark-Simulacrum But there is already let vec = vec![0, -1, 2, 3];
assert_eq!(
vec.iter().copied().flat_map(|x| usize::try_from(x).ok()).collect::<Vec<_>>(),
&[0, 2, 3],
);
assert_eq!(
vec.iter().copied().map_while(|x| usize::try_from(x).ok()).collect::<Vec<_>>(),
&[0],
);
But it does something while predicate returns *by "stop" I mean "return
|
I've found out that |
If that's the only reason it's a while, then all iterator predicates would have to be named as such? So that seems like an odd reason. Plus, the adapter should not fuse at None; the caller may do so of course. This adapter, not fused, would specifically be "just" and_then without any of the "frills" of other iterators atop it. For example, |
I'm not sure if I understood your point correctly, but here you're relying on implementation details. The moment For instance: let vec = vec![Some(1), Some(2), None, Some(3)];
for x in vec.into_iter().map_while(|x| x) {
// the loop body is executed twice
}
|
If what came after the first None didn't matter, then there would be no real reason to fuse iterators. It's not true in my opinion that the rest of the iterator is unspecified; it is very much so specified by all adapters in std at least (or so I would expect). What an iterator does in for loops or via collect and what it returns are different, but both are important. |
This seems somewhat backwards to me. For a plain iterator, what comes after the first
It's only specified whenever All this to say that removing the |
In my opinion, It's not even unspecified (in this case), I'm wondering if there are cases when you need to continue after first |
Hm, maybe we're talking past each other. I don't disagree that an iterator that is not fused may return Some after the first None. But it is not true that what that iterator returns after the first None is arbitrary and can change over time. i.e., not being fused does not let iterators start returning arbitrary data after the first None in some releases. (Which is what "unspecified" in this context means, I think; it means that it is an unstable implementation detail). With this PR, map_while (or as I'm proposing, and_then), has this specification:
Note, that this implies fused-ness if the underlying iterator is fused, but (like most iterator adapters) does not fuse. This seems reasonable to me. However, such a specification does not lead me to call it "map_while" -- there's no while here, there's no state in the adapter itself. It doesn't do something while the underlying adapter is doing something, or the provided closure is continuing to yield elements. It specifically only acts on one iterator at a time. And indeed, we can only make this change because map_while is still unstable -- if it was stabilized, then regardless of whether it was fused officially (i.e., implemented the trait), I would argue that such a behavioral difference would not be acceptable to make. |
I guess this is the source of our disagreement 🙂 It was my understanding/expectation that non-fused iterators are at liberty to change what Regardless of all this, when used "normally" (i.e. stopping on the first |
We do not call Plus, it's not just predicate success -- it's either the predicate succeeding or the inner iterator returning None. |
I'm not sure I follow, |
Hm, I guess it's true in the sense that filter will keep calling your predicate while you return false (vs. returning None); it only cares about the underlying iterator returning None. I would personally prefer |
src/libcore/iter/traits/iterator.rs
Outdated
@@ -1114,8 +1114,23 @@ pub trait Iterator { | |||
/// The `-3` is no longer there, because it was consumed in order to see if | |||
/// the iteration should stop, but wasn't placed back into the iterator. | |||
/// | |||
/// Note that unlike [`take_while`] this iterator is **not** fused: |
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.
I don't think this needs to be documented. The lack of a FusedIterator
impl should be enough to know it's not fused. Although in general if you need a fused iterator you should just call .fuse()
whether it happens to implement FusedIterator
or not.
Additionally I think that the behavior after .next()
has returned None
should be left unspecified, unless of course there is some use case for that behavior. I'd imagine that if you need access to more elements after the predicate has returned None
you'd be better off just using .map(predicate)
.
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.
I disagree in that I think mentioning this iterator not being fused in the docs is a good thing. The lack of impls, especially for marker traits, is easily overlooked.
However, I agree that we probably don't want to specify the behavior after the first None
is returned. At least for now. If it turns out later that there are use cases for a well defined behavior, we can still add it. So I would replace the code example with one sentence and put it all in one paragraph. Something like this:
Note that unlike
take_while
this iterator is not fused. It is also not specified what this iterator returns after the firstNone
is returned. If you need fused iterator, usefuse
.
I think we should either make What advantages does removing the |
The overhead of the It's probably good to note that |
So if we keep Slightly reduced implementation complexity, the |
Are we sure that this actually is a footgun? I've never heard of any bugs that were caused by If non-fused iterators are indeed a footgun for people, then you could argue every iterator should be fused. |
I'm also want to argue about this being a footgun. Most users in most cases will use operations that stop on first I think that |
"Footgun" was probably a too strong of a word. But it still is a strangeness in the API that could lead to bugs. @WaffleLapkin I just took a look at your original PR again. The example in your top comment requires Also, two pieces of the doc comment of
and
I'm just a bit confused as your code and comments on the first PR seem like you very much want |
Oh, my bad, sorry. I've simply added note about unfuseness and forgotten about other parts of the doc ':) I'll fix doc |
Fix doc of `Iterator::map_while` so it would be clearer that it isn't fused.
Sorry for the team ping, bu I think I need some input from @rust-lang/libs here. In summary: this PR changes the unstable
Arguments against this change:
I think the three possible options are: (a) merge as is, (b) rename iterator to better reflect its semantics and merge, (c) decide I know this is just an unstable API and we can change it later anyway, but we might as well decide it now. Otherwise the iterator might accidentally be stabilized without discussing this issue further. |
I'm in favor of merging this. If you rely on fused iterators then you should be using |
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.
Ok, then let's get this merged. I only have two more comments.
src/libcore/iter/traits/iterator.rs
Outdated
@@ -1114,8 +1114,23 @@ pub trait Iterator { | |||
/// The `-3` is no longer there, because it was consumed in order to see if | |||
/// the iteration should stop, but wasn't placed back into the iterator. | |||
/// | |||
/// Note that unlike [`take_while`] this iterator is **not** fused: |
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.
I disagree in that I think mentioning this iterator not being fused in the docs is a good thing. The lack of impls, especially for marker traits, is easily overlooked.
However, I agree that we probably don't want to specify the behavior after the first None
is returned. At least for now. If it turns out later that there are use cases for a well defined behavior, we can still add it. So I would replace the code example with one sentence and put it all in one paragraph. Something like this:
Note that unlike
take_while
this iterator is not fused. It is also not specified what this iterator returns after the firstNone
is returned. If you need fused iterator, usefuse
.
@bors r+ |
📌 Commit e964d71 has been approved by |
☀️ Test successful - checks-azure |
This PR removes
finished
flag fromMapWhile
as been proposed in #66577 (comment).This also resolves open questions of the tracking issue (#68537):
MapWhile
can't implement bothDoubleEndedIterator
(discussed in AddIterator::map_while
#66577 (comment) and following comments)FusedIterator
(this pr removesfinished
flag, soMapWhile
isn't fused anymore)finished
flag, so there is no question in including it in debug output)r? @Mark-Simulacrum