Skip to content

Rust Iterators

An iterator is anything that can produce a sequence of values, one at a time. You’ve been using them since the For page without necessarily naming them — every for loop over a collection is powered by an iterator behind the scenes.

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

Output:

Some(1)
Some(2)
Some(3)
None
▶ Try it Yourself

.next() returns an Option — Some(value) for each item, and None once it runs out. A for loop is really just calling .next() repeatedly under the hood until it gets None.

.map() takes a closure and applies it to every item, producing a new iterator:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Output:

[2, 4, 6, 8, 10]
▶ Try it Yourself

.collect() is what turns the iterator back into a concrete collection (here, a Vec<i32>) — without it, you’d just have an unevaluated iterator sitting there.

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

Output:

[2, 4, 6]
▶ Try it Yourself

The double **n looks unusual — .filter() hands the closure a reference to a reference (&&i32), so it needs dereferencing twice to compare against a plain number. It’s a common little wrinkle; don’t worry about the full explanation yet.

This is where chaining iterator methods starts to pay off — you can combine several steps into one readable pipeline:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // keep even numbers
.map(|n| n * 10) // multiply each by 10
.collect();
println!("{:?}", result);
}

Output:

[20, 40, 60]
▶ Try it Yourself

Compare this to writing the same logic with a manual for loop and an empty Vec you push into — it’s not wrong, but the chained version reads closer to “filter, then multiply, then collect,” which is exactly the sequence of steps you’re describing.

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("sum: {total}, count: {count}, has even: {has_even}");
}

Output:

sum: 15, count: 5, has even: true
▶ Try it Yourself

.sum() adds everything up, .count() tells you how many items there are, and .any() answers “does at least one item match?”. These are just a few of the methods every iterator has. There are many more, like enumerate, zip, take and rev. On the Custom Iterators page you’ll build your own iterator and see that it gets all of these methods for free.

Take the numbers 1 to 10, keep only the odd ones, square each one, and add them all up. Do it in one iterator chain, with no mut variable. Hint: chain .filter(), .map() and .sum(), and give the result a type like let total: i32.

Show solution
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // keep 1, 3, 5, 7, 9
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("sum of odd squares: {total}");
}

Output:

sum of odd squares: 165
▶ Try it Yourself

Rust needs the : i32 on total because .sum() can add up into several number types, and it can’t guess which one you want.

Start with vec!["the", "quick", "brown", "fox", "jumps"]. Build a new Vec<String> that has only the words longer than 3 letters, in uppercase, and print it with {:?}. Hint: .filter(|w| w.len() > 3) keeps the long words, and .to_uppercase() turns a &str into an uppercase String.

Show solution
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

Output:

["QUICK", "BROWN", "JUMPS"]
▶ Try it Yourself

words itself is unchanged. .iter() only borrows it, so you can still use words after the chain.

  • An iterator produces values one at a time via .next(), which returns an Option.
  • for loops are iterators under the hood.
  • .map() transforms each item; .filter() keeps only the ones matching a condition; .collect() turns the result back into a concrete collection.
  • Chaining iterator methods together is usually clearer than a manual loop with a mutable accumulator.

Quick check

1. What does .next() return on an iterator?

2. What does .collect() do at the end of a chain like .iter().map(...).collect()?

3. In .filter(|n| **n % 2 == 0), why is n dereferenced twice?

4. What powers a for loop when it loops over a collection?

5. What does .filter() keep in the resulting iterator?

Score: 0 / 5