Skip to content

StepBy::nth is buggy with non-fused iterators #159965

Description

@theemathas

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-iteratorsArea: IteratorsC-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions