RFA-261 · Case file with fixtures · Case 233 of 694 · Runtime evidence
Iterator::all Short-Circuits and Leaves the Remaining Items
all is a short-circuiting boolean query, not an exhaustive validation pass. Predicate side effects stop at the first false result; loop explicitly when every item must be checked or observed.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- One counterexample determines the universal boolean result, so all deliberately stops pulling the source at the first false predicate.
- First discriminating check
- Count predicate calls and inspect the iterator remainder after placing one false item before the final element.
all answers a universal question, but it does not need to inspect the whole iterator once the answer is known.
The failing program checks whether 2, 3, 4 are all even. Iterator::all sees two, then three, returns false, and leaves four unvisited.
One counterexample completes the answer
As soon as the predicate returns false, no later item can make “all items satisfy this” true. The method stops requesting elements and returns false.
This is normal boolean short-circuiting. It makes all efficient for expensive or infinite sources where a counterexample appears. It also means the predicate is not an exhaustive callback.
When the iterator is consumed by value, the unvisited remainder is later dropped with the iterator. When it is temporarily borrowed through by_ref, caller code can observe and continue it.
The rejected item has already been consumed
The predicate cannot evaluate an item before the iterator yields it. Therefore the first false item is pulled and consumed. The remainder begins after it.
In the fixture, three is not available afterward; four is. This boundary matters when resuming a parser or event stream.
The repaired program asserts the remaining four. It treats this as evidence of state, not necessarily permission to continue a domain protocol.
Validation and observation should be separated
If the predicate logs, updates metrics, mutates shared state, or sends requests, only a prefix receives those effects. A developer may still see the correct false result and miss the incomplete side-effect pass.
I keep predicates pure when possible. If every item must produce a diagnostic, I loop through all items and track the aggregate boolean separately.
let mut valid = true;
for item in items {
if !validate(item) {
valid = false;
}
}
This explicitly pays for complete traversal.
all on empty input is true
An empty iterator contains no counterexample, so all returns true without calling the predicate. This vacuous-truth convention is useful in logic but can surprise request validation.
If a form requires at least one item and every item must be valid, I check both non_empty and all_valid. The universal predicate alone cannot establish presence.
A peekable check or collection-length validation can express presence, depending on whether consuming the source is acceptable.
any has the mirror behavior
Iterator::any stops at the first true result. It also consumes the matching item and leaves later elements unvisited. Both methods are queries that do the minimum work necessary to know their boolean result.
I remember them as existential and universal short circuits, not as loop aliases. for_each visits until exhaustion but cannot break cleanly with a value; try_for_each supports fallible early exit.
Choosing an iterator method includes choosing its stop rule.
Infinite iterators reveal the contract
all over an infinite stream can return false when it finds a counterexample, but it can run forever if every item seen remains true. There is no final exhaustion to prove the universal property.
For live streams I use a time, count, or protocol boundary and phrase the result as “all observed items,” not all future items. The Rust type cannot express that temporal limit by itself.
Side effects in next can also stop
Even with a pure predicate, pulling the source may decode records or perform I/O. Short-circuiting means later source work does not happen.
This can be beneficial, but cleanup and acknowledgement should not depend on exhausting next. I use ownership guards and explicit stream protocols for those requirements.
What I test
My table covers false at the beginning, middle, and end; all true; and empty input. A counted iterator distinguishes predicate calls from underlying pulls and verifies the first rejected item was consumed.
For validation APIs I also assert whether every error is expected or only the first counterexample. The method choice then matches the user-facing promise.
Borrowing the iterator is an intentional signal
Using values.by_ref().all(...) says the caller expects to retain iterator ownership. I treat that as a review point: will later code consume the remainder, and does it understand that the rejected item is already gone?
If resumption has no meaning, moving the iterator into all removes an accidental continuation path. Ownership shape can communicate whether the remaining state is part of the API.
The core principle is that a boolean answer does not imply exhaustive traversal. Iterator::all stops once false is inevitable. This preserves useful laziness, while any requirement to inspect, report, or affect every item needs a separate explicit pass.