-
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
Stabilize step_by by adding it to Iterator (issue #27741) #41439
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @BurntSushi (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
This is my first commit to Rust! :D I may need help with the docstrings. I feel like they can be improved. Also, I couldn't get it to compile without adding the |
src/libcore/iter/iterator.rs
Outdated
#[inline] | ||
#[stable(feature = "rust1", since = "1.0.0")] | ||
fn every_nth(self, step: usize) -> EveryNth<Self> where Self: Sized { | ||
assert!(step != 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.
Not sure if I'd expect this method to panic, especially if I'm using it programmatically. I'm in no position to define this, but: If this assert stays, I'd add a custom message.
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 would personally expect the behaviour of .peek()
for every_nth(0).next()
. Troublesome to implement though.
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.
@nagisa more like impossible without cloning (peak returns a reference, next
would return a value).
From the description ("skipping step
elements at a time"), I'd expect every_nth(0)
to return every item and every_nth(1)
to skip every other item. This would also be consistent with nth
.
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.
Yeah, I think it would be best to have that behavior instead. I'll make the changes now.
src/libcore/tests/iter.rs
Outdated
@@ -145,6 +145,22 @@ fn test_iterator_chain_find() { | |||
} | |||
|
|||
#[test] | |||
fn test_every_nth_one() { |
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.
Maybe add another test (with #[should_panic]
) to test .every_nth(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.
Yes, such a test should be added.
How about if the number passed to |
@ollie27 How would Like this: let mut it = (0..).every_nth(1).take(3);
assert_eq!(it.next(), Some(0));
assert_eq!(it.next(), Some(2));
assert_eq!(it.next(), Some(4));
assert_eq!(it.next(), None); Or like this: let mut it = (0..).every_nth(1).take(3);
assert_eq!(it.next(), Some(1));
assert_eq!(it.next(), Some(3));
assert_eq!(it.next(), Some(5));
assert_eq!(it.next(), None); |
@ivandardi that's a good question. Both seem reasonable to me. |
Probably the first. You can get the behaviour of the 2nd code sample by skipping before hand, whereas you cannot get the first behaviour from the second. |
This is how Python behaves:
|
Hm... It would be good to keep consistency with other languages too. If we keep the Python behavior, then the current implementation is correct and all it's needed is the After that we need to discuss whether or not to keep the name |
Too bad Python has a terrible behaviour in a lot of cases. I feel that at this point it is more in the garden of Here’s what the documentation for the behaviour I would prefer:
Implementation wise it also needs to be decided whether it is fine for iterator to be implemented as
as these two have distinct behaviour in terms of the state the underlying iterator is after a |
I’ll take liberty of tagging this as waiting-on-team, as there’s clearly nothing the author can act on. |
src/libcore/iter/mod.rs
Outdated
fn next(&mut self) -> Option<Self::Item> { | ||
let elt = self.iter.next(); | ||
if self.step > 0 { | ||
self.iter.nth(self.step - 1); |
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.
If nth
returns None
here, then the behavior of calling next
again is unspecified. Shouldn't that be avoided?
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.
Well, the iterator could be fused so that it never returns a Some(T)
again. But I don't know if that's desired behavior, because one could voluntarily .fuse()
beforehand.
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.
But the problem here is that iter.next()
could return Some(..)
and internally hit a None
, but calling next()
again could return Some(..)
. So the user is not actually informed that the iterator returned None
once, making the behavior of every_nth
itself unspecified without using fuse
.
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.
Itertools fuses the iterator internally too, so I don't see why not fuse it here too. We'd just need to leave it explicit in the documentation.
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 can think of a way to avoid fusing. But that would involve consuming an element when every_nth
is called, which might be (slightly?) against the "laziness" principle of iterator combinators.
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.
At least in the basic use case of stepping through ranges, the compiler should be able to inline the whole thing and get rid of every abstraction (the fuse iterator or the first_take
flag, nth
etc). For other use cases, it might be impossible to optimise away the branch, but it might also be very bad to consume elements in advance.
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.
but it might also be very bad to consume elements in advance.
I wasn't clear enough, I think. I said consume "the first element" in advance, not "all." I'm not quite sure if even consuming the first element in advance could be "bad," except perhaps weakly breaking a principle.
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.
Even if the fused/first_take
approaches have the same branching, the current implementation and the one I pointed out do not have the same complexity from the point of view of work on the inner iterator, because of the early nth
.
If the final consumer of the every_nth
iterator only takes the first element, the underlying iterator will consume 1+n elements with the current implementation (as in @nagisa's first option), while one might expect it to only consume 1 element (as in the second option).
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.
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.
@ranma42 I'm guessing, that rationale would indeed matter when the iterator is consuming data from a network stream, for instance. In that case, consuming any element in advance might cause unexpected blocking behavior.
src/libcore/iter/iterator.rs
Outdated
/// assert_eq!(iter.next(), None); | ||
/// ``` | ||
#[inline] | ||
#[stable(feature = "rust1", since = "1.0.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.
You'll want to fix this attribute. It should be given its own feature name and it should be unstable.
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.
Yes, I plan to do that. I just didn't know how when I made the pull request. Also, the feature name depends on whether we get rid of the Step
trait or if we'll keep the step trait and have this as every_nth
. I personally prefer the former, since there's some code that already uses step_by
and wouldn't break as bad with a change in the implementation. That would mean, however, that I'd have to reimplement Iterator for Range
, which is not hard, but would definitely take some time. Once all that gets figured out, I'll put the proper attributes.
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.
Does taking the name step_by
really require removing the Step
trait?
Removing step
, steps_between
, and is_negative
from Step
seems logical, as does replacing core::iter::StepBy
with the logic worked out here. But redoing all the Range
iterators feels like it shouldn't be necessary for this change, and I don't see how the existence of Step
conflicts with it.
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.
Because if we rename every_nth
to step_by
, then there's going to be a method name conflict for ranges, because there would be a step_by
from the Iterator trait, and a step_by
from the Range struct.
Technically we wouldn't need to remove the Step
trait, but if we switch the Range::step_by
implementation to the iterator version, then there's going to be no use for the Step
trait in the core lib. Besides, it looks like the only implementors of Step
are the normal integers. Nobody really uses the Step
trait to increment an integer when they can just use += 1
. And if people really wanted it, we could just implement Iterator
for them and have them return the integer plus one. It would work the same.
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.
@ivandardi I think we should just go with a separate feature name for now. We can do the removal in another PR.
cc @rust-lang/libs Are we sufficiently past the |
@ivandardi The libs team discussed this today, and it seems like we're all on board with this direction. It looks like there are still some changes you wanted to make before merging though? For example, is it intentional that |
@BurntSushi I feel like there has been some mis-comunication? It was on the libs team to decide on one of the many proposed behaviours, and I do not see any decision wrt that. |
@nagisa I see. I'll take a closer look and try to summarize. |
Yes, it's expected that I'll submit a commit making the changes to the feature name and making it unstable hopefully later today. |
You did agree before that having |
@nagisa Nope. Makes more sense for Also, I was testing with usize step = do_calculation()
for i in (0..len).step_by(step) where |
That doesn't match
|
Awesome! Yeah, it took one month to get this going! At least I'm glad it works. After this, there needs to be another PR to take care of Oh, and a last question: Is it my Github name that gets added to the contributors list automatically? If yes, then I just updated it. :D |
⌛ Testing commit 4955517 with merge 2ed2222... |
💔 Test failed - status-appveyor |
Hmm not sure. @steveklabnik ? |
I'm pretty sure it's just the name and email you committed as. |
☀️ Test successful - status-appveyor, status-travis |
Follow-up to rust-lang#41439 (comment) While doing so, remove the now-unused `step`, `steps_between`, and `is_negative` methods from `Step`. Mostly simple, but needed two interesting changes: * Override `Iterator::size_hint` for `iter::StepBy` (so hints aren't lost) * Override `Iterator::size_hint` for `ops::RangeFrom` (so `(0..).size_hint()` returns what `(0..).step_by(1).size_hint()` used to) (It turns out that `(0..).step_by(d)` is used in a bunch of tests, from `cycle` to `vec_deque`.) Incidentally fixes rust-lang#41477
Isn't there a specialization for the |
…alexcrichton Deprecate range-specific `step_by` Deprecation attributes and test updates only. Was replaced by an any-iterator version in rust-lang#41439 Last follow-up (this release) to rust-lang#42110 (comment) r? @alexcrichton
…alexcrichton Deprecate range-specific `step_by` Deprecation attributes and test updates only. Was replaced by an any-iterator version in rust-lang#41439 Last follow-up (this release) to rust-lang#42110 (comment) r? @alexcrichton
…alexcrichton Deprecate range-specific `step_by` Deprecation attributes and test updates only. Was replaced by an any-iterator version in rust-lang#41439 Last follow-up (this release) to rust-lang#42110 (comment) r? @alexcrichton
…alexcrichton Deprecate range-specific `step_by` Deprecation attributes and test updates only. Was replaced by an any-iterator version in rust-lang#41439 Last follow-up (this release) to rust-lang#42110 (comment) r? @alexcrichton
Inspired by itertools'
take()
method. See issue #27741