`event::Iter::size_hint` and `count` report the total, not the remaining
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):
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
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 4Impact
Low, and bounded by a trait that is deliberately absent.
- No unsoundness.
Iterimplements neitherExactSizeIteratornorTrustedLen, 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 fromcount()in the same situation. - Callers who iterate a fresh
Iterto 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
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.
Source: tokio-rs/mio