-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Open
Labels
A-iteratorsArea: IteratorsArea: IteratorsA-specializationArea: Trait impl specializationArea: Trait impl specializationC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.
Description
I tried this code:
pub fn using_each() {
let mut it = 0..4;
it.by_ref().zip(0..2).for_each(|_| {});
println!("{:?}", it);
}
pub fn using_loop() {
let mut it = 0..4;
for _ in it.by_ref().zip(0..2) {}
println!("{:?}", it);
}
pub fn using_each_dyn() {
let mut it = 0..4;
(&mut it as &mut dyn Iterator<Item = i32>).zip(0..2).for_each(|_| {});
println!("{:?}", it);
}
pub fn using_loop_dyn() {
let mut it = 0..4;
for _ in (&mut it as &mut dyn Iterator<Item = i32>).zip(0..2) {}
println!("{:?}", it);
}
fn main() {
using_each();
using_loop();
using_each_dyn();
using_loop_dyn();
}
I expected all four functions to behave identically, but instead the above code outputs:
2..4
3..4
3..4
3..4
This occurs because:
- In the general case, the
Zip
iterator will call.next()
on the first iterator to check if the value isNone
, and then call.next()
on the second iterator. This means that if the second iterator is shorter, then the first iterator will have.next()
called one past the length of the second iterator. - In the
TrustedLen
specialization ofZip
'sfold()
method, if the second iterator is shorter, then both iterators have their.next()
called exactly equal to that second iterator's length.
rust/library/core/src/iter/adapters/zip.rs
Lines 666 to 695 in 231257f
impl<A: TrustedLen, B: TrustedLen> SpecFold for Zip<A, B> { #[inline] fn spec_fold<Acc, F>(mut self, init: Acc, mut f: F) -> Acc where F: FnMut(Acc, Self::Item) -> Acc, { let mut accum = init; loop { let (upper, more) = if let Some(upper) = ZipImpl::size_hint(&self).1 { (upper, false) } else { // Per TrustedLen contract a None upper bound means more than usize::MAX items (usize::MAX, true) }; for _ in 0..upper { let pair = // SAFETY: TrustedLen guarantees that at least `upper` many items are available // therefore we know they can't be None unsafe { (self.a.next().unwrap_unchecked(), self.b.next().unwrap_unchecked()) }; accum = f(accum, pair); } if !more { break; } } accum } }
This mismatch in behavior seems undesirable.
Discovered in #143966.
Meta
Reproducible on the playground with version 1.90.0-nightly (2025-07-15 3014e79f9c8d5510ea7b)
@rustbot labels +A-iterators +A-specialization
mati865
Metadata
Metadata
Assignees
Labels
A-iteratorsArea: IteratorsArea: IteratorsA-specializationArea: Trait impl specializationArea: Trait impl specializationC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.