The Iterator trait

An iterator is a value that produces one item per call to next, and returns None when there is nothing left. That is the whole trait: everything else in the module, from map to collect, is built on that one method. A for loop is sugar for calling it until it stops.

Coming from C++? Iterators and algorithms walks a vector with begin/end and with a Rust iterator, then adds an element during the walk: C++ compiles it and invalidates the iterators, Rust stops at the borrow.

This lesson writes the trait out by hand, which is the best way to see what the adaptors in the next lesson consume. The last section shows the error for a type that looks iterable but is not.

next() and the iterator

values.iter() returns an iterator of references, and next returns Some for each value and None once past the end. The iterator keeps its position, so the calls have to happen in order.

The program asks for four values from a three-element vector. The first three are present, the fourth prints None, and the vector itself is untouched.

fn main() { let values = vec![10, 20, 30]; let mut iter = values.iter(); println!("{:?}", iter.next()); println!("{:?}", iter.next()); println!("{:?}", iter.next()); println!("{:?}", iter.next()); }

rustc --edition 2024 next.rs
./next

Some(10)
Some(20)
Some(30)
None

The method needs a mutable iterator, which is why the binding says mut. The vector is borrowed for as long as the iterator lives, which the lesson on borrowing explains.

An iterator written by hand

Implementing Iterator takes one associated type and one method. The struct holds the state, next updates it and returns the item, and the compiler gets everything else as a default method.

The program counts down from three and prints the values with a for loop. The loop calls next four times, and the fourth call ends it.

struct Countdown { value: u32, } impl Iterator for Countdown { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.value == 0 { return None; } self.value -= 1; Some(self.value + 1) } } fn main() { for value in (Countdown { value: 3 }) { print!("{value} "); } println!(); }

rustc --edition 2024 countdown.rs
./countdown

3 2 1

The parentheses around the struct literal are required in a for header: without them the compiler reads the braces as the loop body. The same iterator can be used with every adaptor from the next lesson.

for is sugar

A for loop calls into_iter on what follows in, then next until None. An array and a vector both work because both have an IntoIterator implementation.

The program walks an array by value and a vector by reference and prints five numbers. The array is copied because its elements are Copy, and the vector is borrowed.

fn main() { let array = [1, 2, 3]; let values = vec![4, 5]; for value in array { print!("{value} "); } for value in &values { print!("{value} "); } println!(); }

rustc --edition 2024 for_loop.rs
./for_loop

1 2 3 4 5

The loop is the same machine as the manual next calls, with the position kept for you. An explicit loop that would change the collection inside is rejected, which the comparison page demonstrates with a vector.

Consumers

A consumer is a method that runs the iterator to the end and returns one value: count, sum, max and nth are four of them. They take the iterator by value, so it is consumed and cannot be reused.

The program counts three values, sums sixty, finds the maximum and the second element. Each call starts from a fresh values.iter(), which is why all four work on the same vector.

fn main() { let values = vec![10, 20, 30]; println!("{}", values.iter().count()); println!("{}", values.iter().sum::<i32>()); println!("{:?}", values.iter().max()); println!("{:?}", values.iter().nth(1)); }

rustc --edition 2024 consumers.rs
./consumers

3
60
Some(30)
Some(20)

sum needs the type of the total, which is why the turbofish ::<i32> appears. The next lesson combines consumers with the adaptors that change the items on the way.

A type that is not an iterator

A for loop needs IntoIterator, and a plain struct does not have it. The compiler reports E0277 and says that the trait is not implemented, which is the difference between a value and a source of values.

The program tries to loop over a unit struct. The help points at the trait, and the fix is the Iterator implementation from the second section or an into_iter method that returns one.

struct Countdown; fn main() { for value in Countdown { println!("{value}"); } }

rustc --edition 2024 not_iterator_err.rs

error[E0277]: `Countdown` is not an iterator
--> not_iterator_err.rs:4:18
|
4 | for value in Countdown {
| ^^^^^^^^^ `Countdown` is not an iterator
|
help: the trait `Iterator` is not implemented for `Countdown`
--> not_iterator_err.rs:1:1
|
1 | struct Countdown;
| ^^^^^^^^^^^^^^^^
= note: required for `Countdown` to implement `IntoIterator`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.

The same error appears for a type that implements Iterator but not IntoIterator, which is why the standard collections implement both. The lesson on iterator adaptors uses the trait in chains.

Exercises

  1. Write an iterator that yields the squares of one to five and print them with a for loop.
  2. Call next on a vec![1, 2] iterator three times and explain the last answer.
  3. Count the values of an iterator with count, then with a loop. Which one is shorter, and which one consumes the iterator?

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does the Iterator trait require an implementor to write?

    Answer

    An associated Item type and a next method

    Everything else has a default implementation built on next and Item.

  2. What does next return when the values run out?

    Answer

    None

    The Option is how the iterator says there is nothing left.

  3. What does a for loop do with the value after in?

    Answer

    Calls into_iter and then next until None

    The loop is sugar over the IntoIterator conversion and repeated next calls.

  4. Which of these is a consumer?

    Answer

    sum

    sum runs the iterator to the end and returns one value; the others return iterators.

  5. What does this program print? fn main() { let values = vec![10, 20]; let mut iter = values.iter(); println!("{:?}", iter.next()); println!("{:?}", iter.next()); println!("{:?}", iter.next()); }

    Answer

    Some(10) / Some(20) / None

    The two values come first, and the third call finds the end.

  6. What does this program print? struct Countdown { value: u32, } impl Iterator for Countdown { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.value == 0 { return None; } self.value -= 1; Some(self.value + 1) } } fn main() { for value in (Countdown { value: 2 }) { println!("{value}"); } }

    Answer

    2 / 1

    The iterator yields two and then one, and the third call returns None.

  7. What does this program print? fn main() { let values = vec![10, 20, 30]; println!("{}", values.iter().count()); println!("{}", values.iter().sum::<i32>()); println!("{:?}", values.iter().max()); }

    Answer

    3 / 60 / Some(30)

    Each call creates a fresh iterator, and the three answers are count, sum and maximum.

  8. What does this program print? fn main() { let array = [1, 2, 3]; let values = vec![4, 5]; for value in array { println!("{value}"); } for value in &values { println!("{value}"); } }

    Answer

    1 / 2 / 3 / 4 / 5

    The array is walked by value and the vector by reference, and both print their items.

  9. What is the first line of the compiler output for this program? struct Countdown; fn main() { for value in Countdown { println!("{value}"); } }

    Answer

    error[E0277]: Countdown is not an iterator

    The struct has no IntoIterator implementation, so the for loop cannot start.

  10. What does this program print? fn main() { let values = vec![1, 2, 3, 4]; let mut iter = values.iter(); iter.next(); println!("{:?}", iter.nth(1)); }

    Answer

    Some(3)

    The first next consumes one, and nth(1) skips the next and returns the third value.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev