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 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
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.
Summary
Iter::size_hintreturns the total number of events in the container rather than the numberof events remaining, so after
kcalls tonext()it over-reports byk.Iter::counthasthe same shape: it ignores the current position and returns the total.
Iterator::size_hintis specified as "the bounds on the remaining length of the iterator", andIterator::countas "the number of iterations", i.e. the count of items the iterator willstill yield.
No unsoundness — see Impact.
The code
src/event/events.rs:203-224(1.2.1):self.posis the read cursor, advanced bynext(). Neithersize_hintnorcountconsults it.Reproduction
Impact
Low, and bounded by a trait that is deliberately absent.
Iterimplements neitherExactSizeIteratornorTrustedLen, so nocaller is entitled to treat the hint as authoritative for unchecked capacity or unchecked
indexing. Adapters use it only as an allocation hint.
collect()/extend()when the iteratorhas been partially consumed, and a wrong answer from
count()in the same situation.Iterto exhaustion — which is the documented and overwhelminglycommon pattern,
for event in &events— see correct behaviour, because at position zero thetotal and the remaining count coincide.
Suggested fix
saturating_subrather than-is required, not stylistic:next()incrementsself.poseven on the call that returns
None(events.rs:212), soposcan exceedlen()once theiterator is exhausted and a plain subtraction would underflow.
Since the corrected
size_hintwould then be exact,impl ExactSizeIterator for Iter<'_>becomesavailable 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 vetcertification of warden's dependency graph,reading
src/event/events.rsagainst theIteratortrait 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-222is unchanged --size_hintreturnsself.inner.inner.len()andcountreturns the same, neither consultingself.pos.