Skip to content

Intermediate · Closures, iterators and smart pointers · topic 32 of 50 · 30 min

Iterators

Explanation

An iterator produces items one at a time through its next() method. iter() borrows items, iter_mut() borrows them mutably, and into_iter() takes ownership. Adapters like map, filter, enumerate and zip build a lazy chain that does nothing until a consumer such as sum, count, collect, fold or find runs it. Iterator chains compile to code as fast as hand-written loops.

Why iterators?

Most data work is "go through a list, keep some items, change them, combine the result". You can write that with for loops and temporary variables, or describe it as a chain of small steps. The chain is often shorter, clearer and just as fast. You have already used iterators: every for loop uses one behind the scenes.

The Iterator trait

rust
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ...plus dozens of provided methods: map, filter, sum, collect...
}

let v = vec![10, 20];
let mut it = v.iter();
println!("{:?}", it.next());   // Some(10)
println!("{:?}", it.next());   // Some(20)
println!("{:?}", it.next());   // None: finished
next() returns Some(item) until the iterator is used up, then None.

type Item is an associated type: each iterator says what kind of item it produces. A for loop simply calls next() until it gets None.

Three ways to iterate a collection

MethodItems areCollection afterwards
v.iter()&T (borrowed)Still usable
v.iter_mut()&mut T (can change)Still usable, maybe changed
v.into_iter()T (owned)Moved; cannot be used

Adapters: building the chain

An adapter takes an iterator and returns a new iterator. Adapters are lazy: they do no work until something asks for items. So a chain of map and filter does nothing on its own; the compiler even warns "unused iterator that must be used".

rust
let prices = vec![120, 45, 300, 80, 999];

let with_gst: Vec<u32> = prices.iter()
    .filter(|&&p| p >= 100)          // keep items of Rs 100 or more
    .map(|&p| p + p * 18 / 100)      // add GST
    .collect();                      // run the chain into a Vec
println!("{:?}", with_gst);          // [141, 354, 1178]
filter receives &&u32 here (a reference to the item reference), so the pattern &&p takes both off.
AdapterWhat it does
map(f)Transform each item
filter(f)Keep items where f returns true
enumerate()Pair each item with its index: (0, a), (1, b)...
zip(other)Pair items from two iterators
take(n) / skip(n)First n items / skip the first n
rev()Go backwards (for iterators that support it)
filter_map(f)Map and drop the None results in one step
chain(other)One iterator after another

Consumers: getting a result

rust
let marks = [72, 91, 45, 88, 30];
let total: u32 = marks.iter().sum();
let passed = marks.iter().filter(|&&m| m >= 40).count();
let top = marks.iter().max();                       // Some(91)
let any_fail = marks.iter().any(|&m| m < 33);       // true
let first_80 = marks.iter().find(|&&m| m >= 80);    // Some(91)
let product = (1..=5).fold(1, |acc, x| acc * x);    // 120
println!("{total} {passed} {:?} {any_fail} {:?} {product}", top, first_80);
Prints: 326 4 Some(91) true Some(91) 120

collect() can build many collections: a Vec, a String, a HashMap from pairs, or a HashSet. Tell it which with a type annotation or the turbofish: collect::<Vec<_>>(). The underscore asks the compiler to fill in the item type.

Zero-cost abstraction

Rust's iterator chains are compiled into a plain loop with no extra allocations or function calls. The Rust Book shows benchmarks where the iterator version is as fast as, or slightly faster than, the hand-written loop, because the compiler can remove bounds checks. You can use the clear style without paying for it.

Common mistakes

  • Writing a chain without a consumer, like v.iter().map(|x| x * 2); and wondering why nothing happened. Add collect, sum or a for loop.
  • Confusing reference levels in closures (&x, &&x). Follow the compiler hints, or use .copied() to turn &u32 items into u32.
  • Calling collect() without saying what to collect into: "type annotations needed".
  • Using into_iter() and then trying to use the collection again.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Iterators · 1/6

next() → Some(x) ... None

An iterator hands out items one at a time. Its next method gives Some item, and finally None.

Prefer a video by a teacher? Find free videos on “rust iterators map filter collect fold tutorial” on YouTube ↗

In real life: Summarising a day of food orders

A cloud kitchen exports orders as (dish, quantity, price) records. One iterator chain filters out cancelled orders, computes each line total and sums revenue; another finds the best-selling dish. The code reads like the business question, and it runs as fast as manual loops.

rust
fn main() {
    let orders = vec![
        ("biryani", 2, 220, false),
        ("dosa", 3, 90, false),
        ("biryani", 1, 220, true),   // cancelled
        ("paneer roll", 4, 120, false),
    ];
    let revenue: u32 = orders.iter()
        .filter(|o| !o.3)
        .map(|o| o.1 * o.2)
        .sum();
    let best = orders.iter()
        .filter(|o| !o.3)
        .max_by_key(|o| o.1)
        .map(|o| o.0);
    println!("Revenue Rs {revenue}, best seller {:?}", best);
}
Prints: Revenue Rs 1190, best seller Some("paneer roll")

Practice (0/3 done)

Task 1 · Question

What does this print? let v = vec![1, 2, 3, 4, 5, 6]; let s: i32 = v.iter().filter(|&&x| x % 2 == 0).map(|x| x * 10).sum(); println!("{s}");

Task 2 · Question

What happens here? let v = vec![1, 2, 3]; v.iter().map(|x| { println!("{x}"); x * 2 });

Task 3 · Practice

Given a Vec of (name, marks) tuples for a class, use iterator chains to: (1) count students who passed (40+), (2) get a Vec of names of students with 75+, (3) find the topper, (4) compute the class average with sum and len. No for loops allowed.

Quick quiz

Question 1

1.What does next() return when the iterator is finished?

Question 2

2.Which method gives mutable references to items?

Question 3

3.What does "lazy" mean for adapters like map?

Question 4

4.What is (1..=4).fold(0, |acc, x| acc + x)?

Question 5

5.Are iterator chains slower than for loops in Rust?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does next() return when the iterator is finished?

Answer: None

next returns None at the end.

Which method gives mutable references to items?

Answer: iter_mut()

iter_mut yields &mut T.

What does "lazy" mean for adapters like map?

Answer: They do nothing until a consumer asks for items

Work happens only when the chain is consumed.

What is (1..=4).fold(0, |acc, x| acc + x)?

Answer: 10

0 + 1 + 2 + 3 + 4 = 10.

Are iterator chains slower than for loops in Rust?

Answer: No, they compile to equally fast code

Iterators are a zero-cost abstraction in optimised builds.

What does next() do when an iterator has no items left?Related

Answer: Raises StopIteration

StopIteration is the signal that the iterator is finished. A for loop catches it for you.

From Iterators and generators

What does an iterator's next() return?Related

Answer: An object { value, done }

next() returns { value, done }.

From Iterators and the iteration protocol

What does it->first give for a std::map iterator?Related

Answer: The key

Map elements are pairs: first is the key, second is the value.

From Iterators: the glue between containers and algorithms

Why does Rust forbid a &mut while & references exist?Related

Answer: So readers never see the value change under them (no data races)

Exclusive access for writers prevents data races and similar bugs.

From References and borrowing

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Closures, iterators and smart pointers” before its exam: Box<T>: values on the heap, Rc, RefCell and Weak.
  2. Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Concurrency and async. 50 topics left in the course before the final exam.

← Previous: Closures