🎓 A1Learner
🔍

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?