Description
This is a tracking issue for the methods Iterator::advance_by
and DoubleEndedIterator::advance_back_by
.
The feature gate for the issue is #![feature(iter_advance_by)]
.
Previously the recommendation was to implement nth
and nth_back
on your iterators to efficiently advance them by multiple elements at once (useful for .skip(n)
and .step_by(n)
). After this feature is stabilized the recommendation will/should be to implement advance_by
and advance_back_by
instead, because they compose better and are often simpler to implement.
Iterators in libcore that wrap another iterator (possibly from elsewhere than libcore) will need to keep their nth
and nth_back
implementations for the foreseeable future and perhaps indefinitely, because the inner iterator may have an efficient nth
implementation without implementing advance_by
as well.
About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
Steps
Implementation history
- Implementation (Add Iterator::advance_by and DoubleEndedIterator::advance_back_by #76909)
- Add tracking issue (Add tracking issue of iter_advance_by feature #77405)
- Implement for
Chain
(Implement advance_by, advance_back_by for iter::Chain #77594) - implement for
slice::{Iter, IterMut}
(Implement advance_by, advance_back_by for slice::{Iter, IterMut} #87387, #[inline] slice::Iter::advance_by #87736) - implement for
vec::IntoIter, ops::Range, iter::{Cycle, Skip, Take, Copied, Flatten}
(implement advance_(back_)_by on more iterators #87091) - Change advance(_back)_by to return the remainder instead of the number of processed elements (Change advance(_back)_by to return the remainder instead of the number of processed elements #92284)
Activity
timvermeulen commentedon Oct 1, 2020
I’ve already written implementations of these methods for most iterators in
core::iter
, I’ll submit PRs for them once I have enough tests.Auto merge of rust-lang#77594 - timvermeulen:chain_advance_by, r=scot…
GuillaumeGomez commentedon Oct 13, 2020
The fact that
advance_by
returns aResult
isn't great in my opinion. In case it moves after the end of the iterator, it's not an error, just that it ran after the end. It would make more sense to returnSome(how_much_after_the_end)
instead ofErr(how_much_after_the_end)
.m-ou-se commentedon Oct 23, 2020
That means that every exising Iterator implementation out there that already has an efficient nth() will now automatically get a default slow advance_by() added, implemented as a next() loop, right? I don't think it can be expected that all these implementations will be updated overnight the moment this hits stable, or even get updated at all. Generic code will then have to make a choice between 1) being efficient/simple with std's Iterators but potentially very slow with other Iterators (
advance_by
) and 2) being efficient with all iterators but with a slightly more confusing function (nth
). :(I like
advance_by
, but I feel like this problem is not something to ignore.m-ou-se commentedon Oct 23, 2020
Returning an
Option
also isn't great.None
in the context of anIterator
usually means the end is reached, so usingNone
here for not reaching the end can also get confusing.I'm wondering about the downside of having to return the 'missing length' from advance_by. I can imagine there are data structures that do not store their length, but do have an upper bound on their length. (E.g. a fixed size buffer containing a C-style 0-terminated string.) Those can just return
None
directly fromnth(n)
ifn
is>=
the maximum length.advance_by(n)
would still require counting the elements to return the rightErr(i)
, even if the caller is probably just going to use.is_ok()
. Having these two similar functions (nth
andadvance_by
) where you can't know in general which one is the more efficient one would be a shame.What are the advantages of returning
Err(missing_length)
as opposed toNone
orfalse
?kevincox commentedon Oct 23, 2020
Since
advance_by
returns the number of items you can't implement in terms of.nth()
which was the previous recommended way to skip elements. (one exception is that you could usenth
for cases wheresize_hint().1 > 0
but this doesn't solve the recursion problem discussed below) I think that returning the number of elements is a useful feature so I wouldn't want to avoid it. This is also something that.nth()
lacks today.However even if it didn't return the length it isn't clear how the default would be implemented. You can't have the defaults of
advance_by
andnth
refer to each other because then if neither was implemented you would have infinite recursion. And if we implementadvance_by(n)
asif n > 0 { self.nth(n-1) }
then for maximum performance you would need to implement bothadvance_by
andnth
. I think it is best to avoid this legacy.So while I admit that it is unfortunate that switching callers from
nth
toadvance_by
can bypass an optimizednth
until the iterator implementation is updated I think that this is the best solution available.timvermeulen commentedon Oct 23, 2020
@m-ou-se
You're absolutely right, which is why we're not going to change any uses of
nth
in libcore toadvance_by
(on potentially user-defined iterator types), even if it would clean up the code. It's an unfortunate side-effect of the fact thatnth
was here first.The main problem that
advance_by
solves is that it composes well in ways thatnth
doesn't for iterators likeChain
. For example, in order to efficiently compute(0..10).chain(10..20).chain(20..30).nth(25)
, it's crucial that the first inner iterator ofChain
doesn't just indicate that it doesn't have ann
th element (or that it isn't able to advance byn
), but that it also somehow indicates how much progress was made in order to know which value ofn
to pass to the second iterator.nth
isn't able to solve this problem on its own.I don't really have a satisfying answer to this... There are indeed iterators out there for which
nth
can be faster thanadvance_by
for certain inputs, although I do believe that these are relatively rare. The best way to handle this might just be to only useadvance_by
if either the iterator type is known to have an equally efficientadvance_by
implementation, or you need theErr(n)
value for something. An example of that is how the current implementation ofChain::nth
callsadvance_by
on the first iterator (because it needs the error value) andnth
on the second.47 remaining items