Have you ever stared at a piece of Rust code you thought was perfect, only to find it does absolutely nothing?
“I told it to print the numbers! Why is my console completely empty?”
You check your logic, the syntax is correct, and you start to question reality. Is your computer haunted?
Don’t worry, you’re not going crazy, and your computer is fine. You’ve just fallen into one of the most famous and fascinating traps of Rust iterators: they are incredibly lazy!
Think of a Rust iterator as a supremely talented but exceptionally lazy martial arts master. You hand them a training manual (like .map()), and they just flip through it, memorizing the moves without any intention of practicing. They won’t lift a finger until you shout, “It’s time to fight!” (by using a method like .collect() or a for loop).
Today, let’s pull back the curtain on this “lazy master” and put an end to your “disappearing code” mysteries once and for all.

Pitfall #1: The map That Does Nothing
This is the classic scene of the “supernatural” event. You write with high expectations:
❌ Incorrect “Mind-Powered” Code:
fn main() {
let nums = vec![1, 2, 3, 4, 5];
// Expecting it to print 1, 2, 3, 4, 5
nums.iter().map(|x| println!("{}", x));
}
The result?
Silence. Nothing happens.
Why is this?
Remember the analogy: .map() is just a “blueprint for transformation.” It tells the iterator, “Hey, your job is to print every number that comes your way.” But the lazy iterator simply takes the blueprint, nods, and does nothing. It hasn’t started its journey because you never gave it the command to “execute.”
✅ The Correct “Forced Execution” Approach:
To get it moving, you need to add a final action at the end of the chain—something we call a “consuming” method.
The most straightforward way is to use for_each, which exists specifically for these kinds of “side-effect” operations that don’t need to return a value.
fn main() {
let nums = vec![1, 2, 3, 4, 5];
// Method 1: Use for_each to consume the iterator
nums.iter().map(|x| println!("{}", x)).for_each(|_| ());
// The closure `|_| ()` is a no-op, like saying: "Just do the work, no need to report back!"
}
However, a more intuitive and idiomatic way is to use a for loop, which is inherently a consuming action.
fn main() {
let nums = vec![1, 2, 3, 4, 5];
// Method 2: The simplest way, just use a for loop
for x in &nums {
println!("{}", x);
}
}
An iterator chain is just a plan. It won’t execute until you give the “go” signal with a consuming method like .collect(), .for_each(), .sum(), or .count().
Pitfall #2: Using a Sledgehammer to Crack a Nut with .collect()
After learning about laziness, beginners often swing to the other extreme: using .collect() for everything.
❌ The “Cannon to Kill a Mosquito” Code:
let nums = vec![1, 2, 3, 4, 5];
// I just want to know how many even numbers there are,
// but I collected them all into a new Vec first.
let even_numbers: Vec<_> = nums.iter().filter(|x| **x % 2 == 0).collect();
let count = even_numbers.len();
This is like ordering everything on the menu just to get the one dish you wanted. Yes, you got what you wanted, but you paid a hefty, unnecessary price (in memory).
The job of .collect() is to gather all the individual items produced by an iterator and assemble them into a brand-new collection (like a Vec or HashMap). If your final goal is just to count, find, or print, this collection step is entirely redundant.
✅ The Correct “On-Demand” Approach:
What do you actually want to do? Just tell the iterator directly!
let nums = vec![1, 2, 3, 4, 5];
// If you just want to count:
let count = nums.iter().filter(|x| **x % 2 == 0).count();
// If you just want to print:
nums.iter().filter(|x| **x % 2 == 0).for_each(|x| println!("{}", x));
.collect() is a powerful finishing move, meant for “assembling the results,” not for “inspecting the process.” Unless you truly need a new collection, don’t use it. Your memory will thank you.
Pitfall #3: The One-Way Trip with into_iter()
The three brothers—iter(), iter_mut(), and into_iter()—are a common source of ownership confusion. into_iter is particularly bossy.
❌ The Tragedy of “Vaporized” Data:
let nums = vec![1, 2, 3];
// into_iter takes ownership of nums
for x in nums.into_iter() {
println!("{}", x);
}
// When you try to use nums again...
println!("{:?}", nums); // The compiler coldly replies: value borrowed here after move!
into_iter() means “turn yourself into an iterator.” This is a destructive, one-way process. Once called, the original collection (nums) has handed over all its contents and effectively “vaporizes.”
✅ The Correct “Clear Ownership” Approach:
Be clear about your intent:
- “I just want to look” (Read-only borrow): Use
.iter(). It gives you a series of read-only references (&T). The original data remains untouched and available. - “I want to make some changes” (Mutable borrow): Use
.iter_mut(). This gives you mutable references (&mut T), allowing you to modify the data in place. The original collection still owns the data. - “I don’t need the collection anymore, just the values” (Transfer ownership): This is the job for
.into_iter(). It gives you the values themselves (T), but at the cost of consuming the original collection.
So, the example above should be corrected like this:
let nums = vec![1, 2, 3];
// I just want to borrow the data for printing, so I use .iter()
for x in nums.iter() { // or simply `for x in &nums`
println!("{}", x);
}
// See? nums is still alive and well!
println!("{:?}", nums);
Think of it this way: iter() is for borrowing a book, iter_mut() is for writing in it, and into_iter() is for taking the book home forever. Confusing them will make your data disappear.
Final Summary
Master these three points, and you’re already ahead of 90% of Rust newcomers. The key to working harmoniously with lazy iterators is to embrace their philosophy: do only what is absolutely necessary, only when it is absolutely necessary.
This “lazy by design” approach is the cornerstone of Rust’s “zero-cost abstractions.” It allows you to build elegant, complex data processing chains that the compiler optimizes into machine code as efficient as a handwritten for loop, with no performance penalty.
So, the next time your code is suspiciously quiet, don’t question your sanity. Just smile and think, “Ah, the master is being lazy again.” Then, give it a clear consuming command and watch it unleash its power.
