RFA-655 · Case file with fixtures · Case 627 of 694 · Runtime evidence
Iterator nth Consumes the Prefix and the Returned Item
nth is a forward-advancing iterator operation, not random access. Borrow a slice with get when later positions must remain independently accessible.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- A stateful relative cursor operation was mistaken for non-mutating absolute random access.
- First discriminating check
- Inspect the iterator's current position and use slice get or enumerate when independent original positions are required.
An iterator represents a position moving through a sequence. nth(n) calls through that progression, discards the next n elements, and returns the following one. That returned item is consumed from the iterator as well. The failing fixture expects 20 twice and instead gets 30 from the next call.
nth is relative to the current position
The Iterator::nth documentation says previously iterated elements are discarded and calling nth(0) is equivalent to next. It takes &mut self because it changes the iterator state.
The next method then continues after the selected item. The call is not an immutable lookup by absolute index.
The repaired fixture uses slice get because the actual intention is random access without consuming traversal state.
Repeated nth calls can skip surprising distances
Calling nth(2) twice does not retrieve original positions two and four by a fixed formula most readers expect. The first call consumes positions zero through two. The second skips two more from the new position and returns original position five.
I use a loop with enumerate when I need several absolute positions during one pass. For an exact random-access collection, indexing or get is clearer. For a streaming source, I maintain the current offset explicitly because rewinding may be impossible.
Calling nth repeatedly on an iterator whose implementation advances one step at a time can also become expensive. Some iterators override it efficiently, but the semantic consumption stays the same.
Lazy sources make skipped work observable
Discarded elements may still be produced by upstream adapters. A map closure can run, a file reader can consume bytes, and a parser can advance input even though the skipped values are never returned to the caller.
I avoid side effects inside iterator transformations when possible. If production has cost or failure, I include skipped paths in tests and observability. nth on Result values does not automatically stop because a skipped item was Err; the iterator item is simply discarded unless another adapter handles it.
For paginated APIs, “skip N” may perform or fetch all previous work and becomes slow at large offsets. Cursor-based continuation is often a better system design.
Ownership follows the iterator item
An owning iterator over Vec<T> moves and drops skipped values. A borrowed iterator yields references and leaves the collection intact, but its traversal state still advances. Consuming the selected item may run Drop when its returned value goes out of scope.
I inspect IntoIterator::Item before assuming skipped data is cheap or preserved. values.iter().nth(n) and values.into_iter().nth(n) have different ownership consequences.
Cloning an iterator can preserve a position only if the iterator implements Clone and cloning its state has the documented meaning. External streams and mutable adapters often cannot or should not be cloned.
Alternatives name different intentions
skip(n).next() is semantically similar and can read better in a longer pipeline. step_by selects periodic items. find searches by predicate. position returns an offset. Slice get performs checked random access.
I choose one matching the question. Using nth to “peek” is wrong; Peekable::peek observes the next item while retaining it for next, though it may initialise the peek buffer by advancing the underlying source once.
Tests start from a non-zero iterator position, call nth zero, request beyond the end, and observe the next item. A counter in the producer confirms how much upstream work happened.
My Iterator nth checklist
- Is n relative to the iterator’s current position or an original collection index?
- Does the caller expect the returned item to remain for the next call?
- What work and side effects occur while skipped items are produced?
- Are skipped owning items moved and dropped?
- Would slice get, enumerate, find, position, skip, or peek express intent better?
- Can repeated nth calls create unexpected skips or poor complexity?
- Is a streaming source impossible or expensive to rewind?
- Do tests assert both the nth result and the iterator state afterward?
The core principle is that iterators are stateful cursors. nth advances that cursor through the requested item. I use it for deliberate forward skipping and choose checked collection access when I need stable positions instead.