Closures and Iterators
# CHAPTER 21
Closures and Iterators
1. Chapter Introduction
Whilefor loops are great, modern programming often favors a "functional" approach. Instead of writing boilerplate loop structures to mutate arrays, we can define *transformations*. In this chapter, we will learn about Closures (anonymous functions that can capture their environment) and Iterators, which allow us to chain powerful data processing methods like map and filter with zero performance penalty.
2. Learning Objectives
By the end of this chapter, you will be able to:- Define and execute Closures.
- Understand how Closures capture environmental variables.
-
Use the
Iteratortrait to process collections.
-
Chain Iterator adapters like
map()andfilter().
-
Consume Iterators using
collect().
3. What are Closures?
Closures are anonymous functions (functions without names) that you can save in a variable or pass as arguments to other functions.Syntax:
Instead of fn and (), closures use pipes | | for parameters.
4. Capturing the Environment
Unlike standardfn functions, Closures can "capture" variables from the scope they were created in!
5. Iterators
An Iterator is an object that allows you to traverse through a collection safely. In Rust, Iterators are Lazy. This means creating an iterator does absolutely nothing until you actively consume it.
6. Iterator Adapters: map and filter
Iterators become incredibly powerful when we use adapters to transform the data cleanly using Closures.
Using map() to transform data:
Using filter() to remove data:
7. The Power of Chaining
You can chain these adapters together to build complex data pipelines effortlessly!*Because of "Zero-Cost Abstractions", this compiles down to code that runs exactly as fast as a manually written, highly optimized for loop!*
8. Common Mistakes
-
Forgetting
.collect(): Because Iterators are lazy, writingnumbers.iter().map(|x| x * 2);does literally nothing. The compiler will warn you that the Iterator is unused. You must consume it with.collect(),.sum(), or a loop.
-
Type inference on
.collect(): You must usually declare the type you are collecting into (e.g.,let v: Vec<i32> = ...), because.collect()can build Vectors, HashMaps, Strings, and more.
9. Best Practices
-
Use
into_iter()when you want ownership:v.iter()yields immutable references (&T).v.into_iter()takes ownership of the vector and yields the actual values (T), which is often easier for map/filter operations where you build a brand new collection.
10. Exercises
-
1.
Create a
Vecof names:["Alice", "Bob", "Charlie"].
-
2.
Use
.into_iter(),.filter(), and.collect()to create a newVeccontaining only names longer than 3 letters.
11. MCQs with Answers
What syntax is used to define parameters for a Closure?
fn function capture a variable declared in the scope outside of it?
a) Yes b) No, only Closures can capture their environment
Answer: b) No, only Closures can.
What does it mean that Rust Iterators are "Lazy"?
Which Iterator adapter applies a Closure to every item and returns the transformed results?
Which Iterator adapter evaluates a Closure and keeps only the items that return true?
What consuming method executes the Iterator chain and gathers the results into a new Collection (like a Vec)?
Why must you often provide a type annotation (e.g., : Vec<i32>) when using .collect()?
.iter() and .into_iter()?
a) There is no difference b) .iter() yields references (&T), while .into_iter() takes ownership of the collection and yields actual values (T)
Answer: b) .iter() yields references, .into_iter() takes ownership.
Q9. Is chaining Iterator methods slower than writing a manual for loop in Rust?
a) Yes, much slower b) No, due to Zero-Cost Abstractions, the compiler optimizes it to be just as fast as a manual loop
Answer: b) No, they compile to identical machine code.
Q10. Can Closures be passed as arguments into functions? a) Yes b) No Answer: a) Yes.
12. Interview Questions
- Q: Explain what a Closure is and how it differs from a standard function in Rust.
- Q: Why are Iterators in Rust considered "Lazy", and what is the benefit of this design?
13. Summary
Closures and Iterators bring powerful functional programming paradigms to Rust without sacrificing speed. By chaining.filter() and .map(), you can transform complex datasets with readable, declarative code. Because Iterators are lazy, no unnecessary memory is allocated until the final .collect() execution.