← Back to Rust Course | Chapter 14: Closures, Iterators & Async | Lesson 2 of 6

The Iterator Trait

The Iterator trait is the recipe every list-walker in Rust follows: it just needs to know how to hand back the next item.

Calling next Manually

Every iterator provides a next() method returning Some(item) while there are more elements, and None once exhausted. A for loop is just repeatedly calling next() under the hood.

Example: Calling next Manually

markup
fn main() {
    let mut iter = vec![1, 2, 3].into_iter();
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
}

Implementing a Custom Iterator

Implementing Iterator for your own type only requires defining next(); the associated Item type declares what kind of value the iterator produces.

Example: Implementing a Custom Iterator

markup
struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let countdown = Countdown(3);
    for n in countdown {
        println!("{}", n);
    }
}

Free Adapter Methods from Iterator

Once a type implements Iterator by defining next(), it automatically gains access to many built-in adapter methods, such as .sum(), without any extra work.

Example: Free Adapter Methods from Iterator

markup
struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let total: u32 = Countdown(4).sum();
    println!("Sum: {}", total);
}

Iterators Are Lazy

Iterator adapters like .map() do not run their closure immediately -- they build a pipeline description that only executes once something consumes the iterator, such as .collect() or a for loop.

Example: Iterators Are Lazy

markup
fn main() {
    let numbers = vec![1, 2, 3];
    let lazy_iter = numbers.iter().map(|n| {
        println!("Processing {}", n);
        n * 2
    });
    let result: Vec<i32> = lazy_iter.collect();
    println!("{:?}", result);
}
Common Mistakes
  1. Forgetting .next() returns an Option<Item>, using None to signal the iterator is exhausted.
  2. Assuming iterators are eager -- most iterator adapters like .map() are lazy and don't run until consumed.
  3. Implementing Iterator but forgetting it only requires defining next(); every other method has a default implementation built on top of it.
Chapter Summary
  • The Iterator trait requires just one method, next(&mut self) -> Option<Self::Item>.
  • Calling .next() repeatedly yields Some(item) until the sequence is exhausted, then returns None.
  • Implementing Iterator for a custom type automatically unlocks dozens of adapter methods like .map() and .filter().
  • Iterators are lazy: adapters build up a pipeline that only actually runs when consumed, such as by .collect() or a for loop.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.