#1990·mio

`event::Iter::size_hint` and `count` report the total, not the remaining

Author: 3x3xX3N0NCreated Jul 30, 2026Updated Jul 30, 2026

Summary

Iter::size_hint returns the total number of events in the container rather than the number of events remaining, so after k calls to next() it over-reports by k. Iter::count has the same shape: it ignores the current position and returns the total.

Iterator::size_hint is specified as "the bounds on the remaining length of the iterator", and Iterator::count as "the number of iterations", i.e. the count of items the iterator will still yield.

No unsoundness — see Impact.

The code

src/event/events.rs:203-224 (1.2.1):

rust
impl<'a> Iterator for Iter<'a> {
    type Item = &'a Event;

    fn next(&mut self) -> Option<Self::Item> {
        let ret = self
            .inner
            .inner
            .get(self.pos)
            .map(Event::from_sys_event_ref);
        self.pos += 1;
        ret
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let size = self.inner.inner.len();
        (size, Some(size))
    }

    fn count(self) -> usize {
        self.inner.inner.len()
    }
}

self.pos is the read cursor, advanced by next(). Neither size_hint nor count consults it.

Reproduction

rust
let mut it = events.iter();          // suppose 4 events are present
let _ = it.next();
let _ = it.next();
assert_eq!(it.size_hint(), (2, Some(2)));  // fails: reports (4, Some(4))
assert_eq!(it.count(), 2);                 // fails: returns 4

Impact

Low, and bounded by a trait that is deliberately absent.

  • No unsoundness. Iter implements neither ExactSizeIterator nor TrustedLen, so no caller is entitled to treat the hint as authoritative for unchecked capacity or unchecked indexing. Adapters use it only as an allocation hint.
  • The observable effects are an over-allocation in collect() / extend() when the iterator has been partially consumed, and a wrong answer from count() in the same situation.
  • Callers who iterate a fresh Iter to exhaustion — which is the documented and overwhelmingly common pattern, for event in &events — see correct behaviour, because at position zero the total and the remaining count coincide.

Suggested fix

rust
    fn size_hint(&self) -> (usize, Option<usize>) {
        let size = self.inner.inner.len().saturating_sub(self.pos);
        (size, Some(size))
    }

    fn count(self) -> usize {
        self.inner.inner.len().saturating_sub(self.pos)
    }

saturating_sub rather than - is required, not stylistic: next() increments self.pos even on the call that returns None (events.rs:212), so pos can exceed len() once the iterator is exhausted and a plain subtraction would underflow.

Since the corrected size_hint would then be exact, impl ExactSizeIterator for Iter<'_> becomes available if upstream wants it — but that is a separate, additive decision and is not required to fix the contract violation.

How this was found

Line-by-line read of mio 1.2.1 during a cargo vet certification of warden's dependency graph, reading src/event/events.rs against the Iterator trait contract.

Not verified by execution: the reproduction above is derived from the source and has not been run against a live Poll.

Upstream context

Checked against the default branch before filing: src/event/events.rs:216-222 is unchanged -- size_hint returns self.inner.inner.len() and count returns the same, neither consulting self.pos.