I tried this code:
// An iterator that, upon calling .next(),
// returns None once, and then returns Some(0) indefinitely
struct Wonky {
exhausted: bool,
}
impl Iterator for Wonky {
type Item = i32;
fn next(&mut self) -> Option<i32> {
if self.exhausted {
Some(0)
} else {
self.exhausted = true;
None
}
}
}
fn main() {
let mut iter = Wonky { exhausted: false }.step_by(1);
println!("{:?}", iter.nth(1));
}
The code should print None, but currently it prints Some(0).
The Wonky { exhausted: false } iterator logically represents an empty iterator. Since it does not implement FusedIterator, its next() method is allowed to return whatever it wants after next() first returns None. See also #t-libs > `next` after `None` and #154238.
Therefore, calling .step_by(1).nth(1), should return None. nth() is being called on an empty iterator, which has length 0, and since 1 > 0, as per the documentation of nth, reproduced below, this method call should return None.
nth() will return None if n is greater than or equal to the length of the iterator.
Relevant code that has the bug:
|
#[inline] |
|
default fn spec_nth(&mut self, mut n: usize) -> Option<I::Item> { |
|
if self.first_take { |
|
self.first_take = false; |
|
let first = self.iter.next(); |
|
if n == 0 { |
|
return first; |
|
} |
|
n -= 1; |
|
} |
|
// n and self.step_minus_one are indices, we need to add 1 to get the amount of elements |
|
// When calling `.nth`, we need to subtract 1 again to convert back to an index |
|
let mut step = self.original_step().get(); |
|
// n + 1 could overflow |
|
// thus, if n is usize::MAX, instead of adding one, we call .nth(step) |
|
if n == usize::MAX { |
|
self.iter.nth(step - 1); |
|
} else { |
|
n += 1; |
|
} |
|
|
|
// overflow handling |
|
loop { |
|
let mul = n.checked_mul(step); |
|
{ |
|
if intrinsics::likely(mul.is_some()) { |
|
return self.iter.nth(mul.unwrap() - 1); |
|
} |
|
} |
|
let div_n = usize::MAX / n; |
|
let div_step = usize::MAX / step; |
|
let nth_n = div_n * n; |
|
let nth_step = div_step * step; |
|
let nth = if nth_n > nth_step { |
|
step -= div_n; |
|
nth_n |
|
} else { |
|
n -= div_step; |
|
nth_step |
|
}; |
|
self.iter.nth(nth - 1); |
|
} |
|
} |
The code here seems to just not check at all whether the iterator has returned None yet or not. It just keeps calling .next() on the underlying iterator and assumes that the iterator hasn't ended yet.
Meta
Reproducible on the playground with version 1.99.0-nightly (2026-07-22 6f72b5dd5f82226a2773)
I tried this code:
The code should print
None, but currently it printsSome(0).The
Wonky { exhausted: false }iterator logically represents an empty iterator. Since it does not implementFusedIterator, itsnext()method is allowed to return whatever it wants afternext()first returnsNone. See also #t-libs > `next` after `None` and #154238.Therefore, calling
.step_by(1).nth(1), should returnNone.nth()is being called on an empty iterator, which has length 0, and since 1 > 0, as per the documentation ofnth, reproduced below, this method call should returnNone.Relevant code that has the bug:
rust/library/core/src/iter/adapters/step_by.rs
Lines 253 to 295 in 5d48869
The code here seems to just not check at all whether the iterator has returned
Noneyet or not. It just keeps calling.next()on the underlying iterator and assumes that the iterator hasn't ended yet.Meta
Reproducible on the playground with version
1.99.0-nightly (2026-07-22 6f72b5dd5f82226a2773)