Skip to main content
Rust Programming
CHAPTER 21 Beginner

Closures and Iterators

Updated: May 18, 2026
5 min read

# CHAPTER 21

Closures and Iterators

1. Chapter Introduction

While for 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 Iterator trait to process collections.
  • Chain Iterator adapters like map() and filter().
  • 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.

rust
12345678910
fn main() {
    // A simple closure assigned to a variable
    let add_one = |x: i32| -> i32 { x + 1 };
    
    // The compiler can usually infer types, making it much shorter!
    let add_two = |x| x + 2;

    println!("5 + 1 = {}", add_one(5));
    println!("5 + 2 = {}", add_two(5));
}

4. Capturing the Environment

Unlike standard fn functions, Closures can "capture" variables from the scope they were created in!
rust
12345678
fn main() {
    let multiplier = 3;

    // This closure captures the 'multiplier' variable from the main scope!
    let multiply = |x| x * multiplier;

    println!("4 * 3 = {}", multiply(4)); // Outputs 12
}

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.
rust
123456789
let v1 = vec![1, 2, 3];

// Creates an iterator, but does NO work yet.
let v1_iter = v1.iter(); 

// The 'for' loop consumes the iterator
for val in v1_iter {
    println!("Got: {}", val);
}

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:

rust
12345678910
fn main() {
    let numbers = vec![1, 2, 3];

    // map() takes a closure and applies it to every item.
    // Iterators are lazy, so we MUST call .collect() at the end to execute the chain 
    // and gather the results into a new Vector!
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();

    println!("Doubled: {:?}", doubled); // [2, 4, 6]
}

Using filter() to remove data:

rust
123456789
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];

    // filter() keeps items where the closure returns 'true'
    // Notice the double reference &&x. .iter() produces &x, and filter borrows it again.
    let evens: Vec<i32> = numbers.into_iter().filter(|x| x % 2 == 0).collect();

    println!("Evens: {:?}", evens); // [2, 4, 6]
}

7. The Power of Chaining

You can chain these adapters together to build complex data pipelines effortlessly!
rust
123456
let numbers = vec![1, 2, 3, 4, 5];

let processed: Vec<i32> = numbers.into_iter()
    .filter(|x| x > &2)    // Keep 3, 4, 5
    .map(|x| x * 10)       // Multiply by 10
    .collect();            // Execute and gather into [30, 40, 50]

*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, writing numbers.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. 1. Create a Vec of names: ["Alice", "Bob", "Charlie"].
  1. 2. Use .into_iter(), .filter(), and .collect() to create a new Vec containing only names longer than 3 letters.

11. MCQs with Answers

Question 1

What syntax is used to define parameters for a Closure?

Q2. Can a standard 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.
Question 3

What does it mean that Rust Iterators are "Lazy"?

Question 4

Which Iterator adapter applies a Closure to every item and returns the transformed results?

Question 5

Which Iterator adapter evaluates a Closure and keeps only the items that return true?

Question 6

What consuming method executes the Iterator chain and gathers the results into a new Collection (like a Vec)?

Question 7

Why must you often provide a type annotation (e.g., : Vec<i32>) when using .collect()?

Q8. What is the difference between .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.

14. Next Chapter Recommendation

Our code has been running on a single CPU core. Modern processors have dozens of cores! In Chapter 22: Concurrency in Rust, we will finally see Rust's legendary "Fearless Concurrency" in action by spawning threads and passing messages.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·