Rust Iterator Size Hints
size_hint is a method that reports how many items an iterator has left. The report is a lower bound and an optional upper bound.
An exact hint on a Countdown
This Countdown knows precisely how many items remain, so both halves of the hint are the same number:
struct Countdown { left: u32 }
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.left == 0 { None } else {
let current = self.left;
self.left -= 1;
Some(current)
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.left as usize;
(left, Some(left))
}
}
impl ExactSizeIterator for Countdown {}len() comes free
With the marker impl in place, len() reads the exact hint — no counting, no pulling:
let it = Countdown { left: 3 };
println!("hint = {:?} len = {}", it.size_hint(), it.len());
let values: Vec<u32> = Countdown { left: 3 }.collect();
println!("{values:?}");An endless iterator promises nothing
The Fibonacci generator from Custom Iterators never ends, so it keeps the default hint. (0, None) is not a lie — it is a refusal to guess:
struct Fibonacci { curr: u32, next: u32 }
impl Iterator for Fibonacci {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
let new_next = self.curr + self.next;
self.curr = self.next;
self.next = new_next;
Some(current)
}
}
let fib = Fibonacci { curr: 0, next: 1 };
println!("fib hint = {:?}", fib.size_hint());
let first_eight: Vec<u32> = Fibonacci { curr: 0, next: 1 }.take(8).collect();
println!("{first_eight:?}");Adapters adjust the promise
Watch what each adapter does to a Vec iterator's exact hint of (4, Some(4)):
let base = vec![1, 2, 3, 4];
println!("vec iter hint = {:?}", base.iter().size_hint());
println!("mapped hint = {:?}", base.iter().map(|n| n * 2).size_hint());
println!("filtered hint = {:?}", base.iter().filter(|n| **n > 1).size_hint());
println!("taken hint = {:?}", base.iter().take(2).size_hint());Example
struct Countdown {
left: u32,
}
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.left == 0 {
None
} else {
let current = self.left;
self.left -= 1;
Some(current)
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.left as usize;
(left, Some(left))
}
}
impl ExactSizeIterator for Countdown {}
struct Fibonacci {
curr: u32,
next: u32,
}
impl Iterator for Fibonacci {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
let new_next = self.curr + self.next;
self.curr = self.next;
self.next = new_next;
Some(current)
}
}
fn main() {
let countdown = Countdown { left: 5 };
println!("countdown len = {}", countdown.len());
let launched: Vec<u32> = countdown.collect();
println!("launched = {launched:?} capacity = {}", launched.capacity());
let fib = Fibonacci { curr: 0, next: 1 };
println!("fib hint = {:?}", fib.size_hint());
let fibs: Vec<u32> = Fibonacci { curr: 0, next: 1 }.take(6).collect();
println!("fibs = {fibs:?}");
let mut rest = Countdown { left: 4 };
println!("next = {:?}", rest.next());
println!("hint now = {:?}", rest.size_hint());
}Key points
- size_hint() returns a pair, (lower, Option<upper>).
- At least lower items remain.
- At most upper remain, when the upper half is Some.
- The promise must never lie.
- A lower bound that is too high breaks library code that trusts it.
- An upper bound that is too low breaks it too.
- When unsure, return (0, None).
- That is the honest default every iterator starts with.
- Adapters keep the promise as far as they can.
- map keeps both bounds.
- filter drops its lower bound to 0.
- It cannot know what will pass.
- take caps both bounds at n.
- Write impl ExactSizeIterator when the count is exact and always known.
- It is an empty marker impl.
- And len() comes free from your size_hint.
- collect reads the lower bound to reserve space: an honest hint can mean one allocation instead of repeated growing
- An endless iterator reports (0, None): that pair means "no promise", not "empty"
- It is not a one-time label.
- As next() hands items out, the hint must shrink.
- It must match what is left.
- size_hint reports how many items an iterator has left, as a lower bound and an optional upper bound.
In this example, Countdown writes size_hint honestly. left items remain. So the hint is (left, Some(left)). The empty ExactSizeIterator impl turns that into a free len(). So countdown len = 5, without pulling a single item. collect trusts the lower bound. It reserves room up front. That is why the launched Vec ends with capacity = 5. That is exactly its length. Fibonacci, sitting beside it, writes no size_hint at all. It never ends. So its hint stays the default (0, None). And take(6) still collects its first six values happily. A missing promise never blocks a consumer. The last lines show the live contract. One next() takes the 4. The remaining Countdown then reports hint now = (3, Some(3)).
Note
size_hint is a live contract.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
An iterator has exactly 3 items left and knows it. What is its honest size_hint?