Working Code
In Phase 1 you used for loops to iterate over vectors. Iterators let you do the same work in a different style:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// for loop approach
let mut doubled_loop = Vec::new();
for n in &numbers {
doubled_loop.push(n * 2);
}
// Iterator chain approach
let doubled_iter: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("for loop: {:?}", doubled_loop);
println!("Iterator: {:?}", doubled_iter);
// Both produce [2, 4, 6, 8, 10]
}
Iterator chains express what you want to do declaratively. The closures from Lesson 22 shine here!
Try It Yourself
- Run
numbers.iter().filter(|n| **n > 3).collect::<Vec<_>>(). What's the result? - Combine
mapandfilterto "double the numbers greater than 3."
iter() vs into_iter() vs iter_mut()
The difference between these three is ownership — the same concept from Phase 2!
| Method | Element type | Original usable? | Analogy |
| ------------- | ---------------------- | ---------------- | ----------------------- |
| iter() | &T (immutable ref) | Yes | Reading a book |
| iter_mut() | &mut T (mutable ref) | Yes (modified) | Writing notes in a book |
| into_iter() | T (ownership moves) | No | Taking the book |
fn main() {
// iter() — borrow and read
let names = vec!["Alice", "Bob", "Carol"];
let lengths: Vec<usize> = names.iter().map(|n| n.len()).collect();
println!("Lengths: {:?}", lengths);
println!("Original: {:?}", names); // still usable
// iter_mut() — borrow and modify
let mut scores = vec![80, 90, 75];
scores.iter_mut().for_each(|s| *s += 5);
println!("Adjusted: {:?}", scores); // [85, 95, 80]
// into_iter() — takes ownership
let words = vec![String::from("hello"), String::from("world")];
let upper: Vec<String> = words.into_iter().map(|w| w.to_uppercase()).collect();
println!("Uppercased: {:?}", upper);
// println!("{:?}", words); // error! ownership moved
}
Tip:
for item in &vecis the same asvec.iter(), andfor item in vecis the same asvec.into_iter()!
Key Iterator Adapters
map — Transform
Transform each element into a different value:
fn main() {
let names = vec!["alice", "bob", "carol"];
let greeting: Vec<String> = names
.iter()
.map(|name| format!("Hello, {name}!"))
.collect();
println!("{:?}", greeting);
// ["Hello, alice!", "Hello, bob!", "Hello, carol!"]
}
filter — Keep Only Matching Elements
Keep only elements that satisfy a condition:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens: Vec<&i32> = numbers
.iter()
.filter(|n| *n % 2 == 0)
.collect();
println!("Evens: {:?}", evens); // [2, 4, 6, 8, 10]
}
enumerate — Pair With Index
Attach an index to each element. Handy when you need a counter in a loop:
fn main() {
let fruits = vec!["apple", "banana", "grape"];
// for loop + manual index
let mut i = 0;
for fruit in &fruits {
println!("{i}: {fruit}");
i += 1;
}
// enumerate — much cleaner!
for (i, fruit) in fruits.iter().enumerate() {
println!("{i}: {fruit}");
}
}
zip — Pair Two Collections
Combine two iterators into pairs:
fn main() {
let names = vec!["Alice", "Bob", "Carol"];
let scores = vec![95, 87, 92];
let report: Vec<String> = names
.iter()
.zip(scores.iter())
.map(|(name, score)| format!("{name}: {score}"))
.collect();
println!("{:?}", report);
// ["Alice: 95", "Bob: 87", "Carol: 92"]
}
fold — Reduce to a Single Value
Accumulate all elements into one value. Similar to reduce in other languages:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Sum
let sum = numbers.iter().fold(0, |acc, n| acc + n);
println!("Sum: {sum}"); // 15
// String concatenation
let words = vec!["Rust", " is", " great"];
let sentence = words.iter().fold(String::new(), |mut acc, w| {
acc.push_str(w);
acc
});
println!("{sentence}"); // Rust is great
}
Tip: For a simple sum,
numbers.iter().sum::<i32>()is more concise.
for Loop vs Iterator Chain
Let's compare both approaches on the same task:
Task: From a list of scores, select those >= 60, add a 10-point bonus, and compute the total.
fn main() {
let scores = vec![45, 82, 67, 93, 55, 78];
// for loop approach
let mut total_loop = 0;
for score in &scores {
if *score >= 60 {
total_loop += score + 10;
}
}
// Iterator chain approach
let total_iter: i32 = scores
.iter()
.filter(|s| **s >= 60)
.map(|s| s + 10)
.sum();
println!("for loop: {total_loop}");
println!("Iterator: {total_iter}");
// Both produce 350
}
The iterator chain reads like a pipeline: filter (>= 60) -> transform (+10) -> sum.
"Why?" — Lazy Evaluation
Iterator adapters (map, filter, etc.) use lazy evaluation. Nothing runs until you call a consuming adapter like .collect() or .sum().
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// No computation happens here!
let lazy = numbers.iter().map(|n| {
println!("Processing: {n}");
n * 2
});
println!("--- before collect ---");
// Computation starts when collect() is called!
let result: Vec<&i32> = lazy.collect();
println!("Result: {:?}", result);
}
Run this and "--- before collect ---" prints first, then the "Processing: ..." lines appear.
This means unnecessary computation is avoided. For example, filter followed by take(3) stops as soon as 3 matching items are found.
Exercise 1 (easy). Rewrite this for loop as an iterator chain.
fn main() {
let words = vec!["hello", "world", "rust"];
// Rewrite this as an iterator chain
let mut uppercased = Vec::new();
for word in &words {
uppercased.push(word.to_uppercase());
}
println!("{:?}", uppercased);
}
Exercise 2 (medium). Use iterator methods to find the range (max - min) of a score vector.
fn score_range(scores: &[i32]) -> Option<i32> {
// Complete this
// Hint: use iter().max() and iter().min()
// Both return Option<&i32>
}
fn main() {
let scores = vec![72, 95, 68, 88, 91];
match score_range(&scores) {
Some(range) => println!("Score range: {range}"), // 27
None => println!("No scores"),
}
}
Exercise 3 (challenge). From a vector of strings, count words with 5+ characters and collect them uppercased into a new vector. Use iterator chains.
fn main() {
let words = vec!["hi", "hello", "rust", "programming", "code", "iterator"];
// Count of words with 5+ characters: ???
// Those words uppercased: ???
}
Q1. What is the difference between iter() and into_iter()?
- A)
iter()is faster - B)
into_iter()returns immutable references - C)
iter()borrows for reading;into_iter()takes ownership - D) No difference
Q2. What does this code print?
fn main() {
let nums = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = nums
.iter()
.filter(|n| **n > 2)
.map(|n| n * 10)
.collect();
println!("{:?}", result);
}
- A)
[10, 20, 30, 40, 50] - B)
[1, 2, 3, 4, 5] - C)
[30, 40, 50] - D)
[3, 4, 5]
Q3. What is "lazy evaluation" for iterators?
- A) Iterators run slowly
- B) Iterators always compute all elements up front
- C) Computation only runs when a consuming adapter like
collect()is called - D)
map()andfilter()return results immediately