Bird
Raised Fist0
Rustprogramming~15 mins

Iterating over collections in Rust - Deep Dive

Choose your learning style10 modes available
Overview - Iterating over collections
What is it?
Iterating over collections means going through each item in a group one by one. Collections are like boxes holding many values, such as lists or arrays. Rust provides ways to visit each item safely and efficiently. This helps you process or use every element in the collection.
Why it matters
Without iteration, you would have to manually access each item by its position, which is slow and error-prone. Iteration lets you write simple code that works for any size of collection. It makes programs flexible and able to handle changing data easily. This is essential for tasks like searching, filtering, or transforming data.
Where it fits
Before learning iteration, you should understand basic Rust syntax, variables, and collections like arrays and vectors. After mastering iteration, you can learn about advanced topics like closures, iterator adapters, and functional programming patterns in Rust.
Mental Model
Core Idea
Iteration is the process of visiting each item in a collection one after another to perform actions on them.
Think of it like...
Iterating over a collection is like reading a book page by page from start to finish, making notes on each page as you go.
Collection: [item1, item2, item3, ..., itemN]
Iteration flow:
┌───────┐   ┌───────┐   ┌───────┐       ┌───────┐
│ item1 │ → │ item2 │ → │ item3 │ → ... → │ itemN │
└───────┘   └───────┘   └───────┘       └───────┘
Build-Up - 7 Steps
1
FoundationUnderstanding collections in Rust
🤔
Concept: Learn what collections are and how Rust stores multiple values.
Rust has collections like arrays and vectors that hold many values of the same type. Arrays have fixed size, vectors can grow or shrink. You can create a vector with let v = vec![1, 2, 3]; and access items by index like v[0].
Result
You can store and access multiple values in one variable.
Knowing how collections hold data is key before learning how to visit each item.
2
FoundationBasic for loop iteration
🤔
Concept: Use a for loop to visit each item in a collection simply.
Rust's for loop lets you write: for item in &v { println!("{}", item); } to print each value. The &v means you borrow the vector to avoid moving it. The loop runs once per item.
Result
Each item in the vector is printed on its own line.
The for loop hides the complexity of visiting items one by one, making iteration easy.
3
IntermediateUsing iterators explicitly
🤔Before reading on: do you think iter() returns owned items or references? Commit to your answer.
Concept: Rust collections provide iterators that produce items one at a time, either by reference or by value.
Calling v.iter() returns an iterator over references to items. You can use methods like next() to get items step-by-step. For example: let mut it = v.iter(); while let Some(item) = it.next() { println!("{}", item); } This loop manually pulls items from the iterator.
Result
Each item is printed, same as for loop but more control.
Understanding iterators as objects that produce items helps grasp Rust's powerful iteration system.
4
IntermediateConsuming iterators with for loop
🤔Before reading on: does a for loop take ownership of the collection or borrow it? Commit to your answer.
Concept: A for loop consumes an iterator, which can borrow or own the collection's items depending on the iterator type.
You can iterate by reference (v.iter()), by mutable reference (v.iter_mut()), or by value (v.into_iter()). For example: for item in v.iter_mut() { *item += 1; } This changes each item in the vector.
Result
The vector's items are incremented by one.
Knowing how different iterator types affect ownership and mutability is crucial for safe Rust code.
5
IntermediateIterator adapters and chaining
🤔Before reading on: do you think iterator methods create new collections or transform items on the fly? Commit to your answer.
Concept: Iterator adapters like map, filter, and take create new iterators that transform or select items lazily without creating new collections immediately.
Example: let doubled: Vec<_> = v.iter().map(|x| x * 2).collect(); This doubles each item and collects results into a new vector. The map adapter applies a function to each item lazily.
Result
A new vector with doubled values is created.
Understanding lazy evaluation in iterators allows writing efficient, composable data transformations.
6
AdvancedCustom iterator implementation
🤔Before reading on: do you think you can create your own iterator types in Rust? Commit to your answer.
Concept: You can define your own iterator by implementing the Iterator trait, controlling how items are produced.
Example: struct Counter { count: u32 } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } } This iterator counts from 1 to 5.
Result
Using Counter in a for loop prints numbers 1 to 5.
Knowing how to build custom iterators unlocks powerful, reusable data sources.
7
ExpertIterator internals and zero-cost abstraction
🤔Before reading on: do you think Rust's iterators add runtime overhead compared to manual loops? Commit to your answer.
Concept: Rust's iterators use traits and inlining to compile down to code as efficient as manual loops, with no extra runtime cost.
The Iterator trait uses generics and monomorphization so the compiler generates specialized code for each iterator chain. This means complex iterator chains run as fast as simple loops. The compiler removes abstraction overhead.
Result
Iterator-based code runs with performance equal to hand-written loops.
Understanding zero-cost abstractions explains why Rust encourages iterator use for both clarity and speed.
Under the Hood
Rust collections provide iterator objects that implement the Iterator trait. Each iterator has a next() method that returns the next item wrapped in Some or None when done. The compiler uses generics and monomorphization to generate specialized code for each iterator chain, eliminating runtime overhead. Borrowing rules ensure safe access during iteration without data races or invalid memory.
Why designed this way?
Rust's iterator design balances safety, flexibility, and performance. Using traits and generics allows writing reusable iteration code without sacrificing speed. The choice to separate iteration from collection storage enables powerful adapter chains. This design avoids runtime costs common in other languages and fits Rust's goals of zero-cost abstractions and memory safety.
┌───────────────┐
│  Collection   │
└──────┬────────┘
       │ provides
       ▼
┌───────────────┐
│   Iterator    │
│ (implements  │
│  Iterator     │
│   trait)     │
└──────┬────────┘
       │ calls next()
       ▼
┌───────────────┐
│   Item or     │
│   None (end)  │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does iter() give you ownership of items or references? Commit to your answer.
Common Belief:Calling iter() on a collection gives you ownership of each item.
Tap to reveal reality
Reality:iter() returns references to items, not ownership. To get ownership, use into_iter().
Why it matters:Misunderstanding ownership can cause compile errors or unexpected moves, breaking code safety.
Quick: Does chaining iterator adapters create intermediate collections? Commit to your answer.
Common Belief:Each iterator adapter like map or filter creates a new collection immediately.
Tap to reveal reality
Reality:Iterator adapters are lazy and only produce items when consumed, avoiding extra allocations.
Why it matters:Thinking adapters create collections leads to inefficient code and confusion about performance.
Quick: Can you safely mutate a collection while iterating over it? Commit to your answer.
Common Belief:You can modify a collection's items freely while iterating over it with iter().
Tap to reveal reality
Reality:iter() gives immutable references; to mutate, use iter_mut(). Modifying the collection's structure during iteration is unsafe.
Why it matters:Wrong mutation during iteration can cause runtime bugs or violate Rust's safety guarantees.
Quick: Do Rust iterators add runtime overhead compared to manual loops? Commit to your answer.
Common Belief:Using iterators is slower than writing manual loops because of abstraction layers.
Tap to reveal reality
Reality:Rust's compiler optimizes iterators to zero-cost abstractions, making them as fast as manual loops.
Why it matters:Believing iterators are slow may discourage their use, missing out on clearer and safer code.
Expert Zone
1
Iterator chains are fully inlined and optimized by the compiler, so complex chains do not slow down your program.
2
Using into_iter() on collections like Vec consumes the collection, transferring ownership and allowing mutation or moving out items.
3
Custom iterators can implement additional traits like DoubleEndedIterator or ExactSizeIterator to enable more efficient or flexible iteration patterns.
When NOT to use
Iteration is not ideal when you need random access or indexing frequently; direct indexing or slices are better. For parallel processing, use crates like Rayon instead of standard iterators. Also, avoid iteration when working with infinite or very large streams without proper termination or buffering.
Production Patterns
In real-world Rust code, iterators are used extensively for data processing pipelines, lazy evaluation, and chaining transformations. Libraries use iterators to provide flexible APIs. Custom iterators implement domain-specific data sources. Iterator adapters help write concise, readable, and efficient code in production systems.
Connections
Functional programming
Iterator adapters like map and filter build on functional programming concepts of pure functions and lazy evaluation.
Understanding iteration in Rust helps grasp functional patterns common in many languages, improving code expressiveness.
Memory safety
Rust's iteration enforces borrowing rules that prevent data races and invalid memory access during traversal.
Knowing how iteration ties to Rust's ownership model deepens understanding of safe concurrent programming.
Assembly language loops
Iteration abstracts the low-level concept of loops and jumps in assembly, providing a high-level, safe interface.
Seeing iteration as a safe, structured loop helps appreciate how high-level languages improve programmer productivity and safety.
Common Pitfalls
#1Trying to modify a vector while iterating with iter() causing compile error.
Wrong approach:for item in v.iter() { *item += 1; // error: cannot assign to *item because it is behind a & reference }
Correct approach:for item in v.iter_mut() { *item += 1; }
Root cause:Using iter() gives immutable references, so mutation is not allowed.
#2Using into_iter() on a vector but still trying to use the vector afterward causing move error.
Wrong approach:for item in v.into_iter() { println!("{}", item); } println!("Vector length: {}", v.len()); // error: use of moved value
Correct approach:for item in v.iter() { println!("{}", item); } println!("Vector length: {}", v.len());
Root cause:into_iter() consumes the vector, moving ownership and invalidating v.
#3Collecting iterator without specifying type causing compilation failure.
Wrong approach:let doubled = v.iter().map(|x| x * 2).collect(); // error: cannot infer type
Correct approach:let doubled: Vec<_> = v.iter().map(|x| x * 2).collect();
Root cause:Rust needs explicit type annotation to know what collection to create.
Key Takeaways
Iteration lets you visit each item in a collection safely and efficiently without manual indexing.
Rust's iterators provide flexible ways to borrow, mutate, or consume collections with zero runtime cost.
Iterator adapters enable powerful, lazy data transformations that compose cleanly and perform well.
Understanding ownership and borrowing is essential to use iteration correctly and avoid common errors.
Custom iterators and traits allow you to extend Rust's iteration model for specialized needs.

Practice

(1/5)
1. What does the following Rust code do?
let numbers = [1, 2, 3];
for num in numbers {
    println!("{}", num);
}
easy
A. Causes a compile error because arrays can't be iterated
B. Prints each number in the array on a new line
C. Prints the whole array as one line
D. Prints the length of the array

Solution

  1. Step 1: Understand the for loop over an array

    The for loop visits each element in the array named 'numbers'.
  2. Step 2: Action inside the loop

    Each element is printed on its own line using println! macro.
  3. Final Answer:

    Prints each number in the array on a new line -> Option B
  4. Quick Check:

    for loop over array prints elements one by one [OK]
Hint: For loops visit each item in order [OK]
Common Mistakes:
  • Thinking arrays can't be iterated
  • Expecting the whole array printed at once
  • Confusing print! with println!
2. Which of these is the correct syntax to iterate over a vector named v in Rust?
easy
A. for i in v { println!("{}", i); }
B. for i to v { println!("{}", i); }
C. foreach i in v { println!("{}", i); }
D. loop i in v { println!("{}", i); }

Solution

  1. Step 1: Recall Rust for loop syntax

    Rust uses 'for variable in collection' to iterate over items.
  2. Step 2: Check each option

    for i in v { println!("{}", i); } matches correct syntax; others use invalid keywords.
  3. Final Answer:

    for i in v { println!("{}", i); } -> Option A
  4. Quick Check:

    Rust uses 'for ... in ...' loops [OK]
Hint: Remember: for variable in collection [OK]
Common Mistakes:
  • Using 'to' instead of 'in'
  • Using 'foreach' which is not Rust syntax
  • Using 'loop' incorrectly
3. What is the output of this Rust code?
let v = vec![10, 20, 30];
let mut sum = 0;
for &num in &v {
    sum += num;
}
println!("{}", sum);
medium
A. 60
B. 303020
C. Error due to borrowing
D. 0

Solution

  1. Step 1: Understand iteration with references

    The loop uses '&num in &v' to borrow each element by reference and dereference it with '&num'.
  2. Step 2: Calculate sum of elements

    sum starts at 0, then adds 10, 20, and 30, resulting in 60.
  3. Final Answer:

    60 -> Option A
  4. Quick Check:

    Sum of 10+20+30 = 60 [OK]
Hint: Sum elements by iterating with references [OK]
Common Mistakes:
  • Confusing references and values
  • Expecting concatenation instead of sum
  • Thinking borrowing causes error here
4. Identify the error in this Rust code snippet:
let arr = [1, 2, 3];
for num in arr {
    num += 1;
    println!("{}", num);
}
medium
A. Loop variable 'num' must be a reference
B. Syntax error: missing semicolon after println!
C. Array cannot be iterated directly
D. Cannot modify 'num' because it is an immutable copy

Solution

  1. Step 1: Check mutability of loop variable

    In Rust, loop variables are immutable by default; 'num' is a copy of array element.
  2. Step 2: Understand why 'num += 1' fails

    Trying to change 'num' causes error because it is not declared mutable.
  3. Final Answer:

    Cannot modify 'num' because it is an immutable copy -> Option D
  4. Quick Check:

    Loop variables are immutable unless declared mutable [OK]
Hint: Loop vars are immutable unless 'mut' used [OK]
Common Mistakes:
  • Thinking semicolon missing causes error
  • Believing arrays can't be iterated
  • Assuming loop vars are references by default
5. You have a vector of strings: let words = vec!["apple", "banana", "cherry"];. How do you create a new vector containing the lengths of each word using iteration?
hard
A. let lengths = words.map(|w| w.len());
B. let lengths = for w in words { w.len() };
C. let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect();
D. let lengths = words.iter().for_each(|w| w.len());

Solution

  1. Step 1: Use iterator and map to transform elements

    words.iter() creates an iterator over references to strings; map applies a function to each element.
  2. Step 2: Collect results into a new vector

    collect() gathers the mapped lengths into a Vec<usize>.
  3. Final Answer:

    let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect(); -> Option C
  4. Quick Check:

    Use iter().map(...).collect() to transform collections [OK]
Hint: Use iter().map(...).collect() to build new collections [OK]
Common Mistakes:
  • Trying to use for loop as expression
  • Using map directly on vector without iter()
  • Using for_each which returns () not a collection
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard