Skip to content

The Zip iterator does not update the underlying iterator correctly in its TrustedLen specialization. #144012

Open
@theemathas

Description

@theemathas

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 is None, 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 of Zip's fold() method, if the second iterator is shorter, then both iterators have their .next() called exactly equal to that second iterator's length.
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-iteratorsArea: IteratorsA-specializationArea: Trait impl specializationC-discussionCategory: Discussion or questions that doesn't represent real issues.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions