Skip to content

Rust Closures & Iterators — Functional Programming

DodaTech Updated 2026-06-21 7 min read

In this tutorial, you'll learn about Rust Closures & Iterators. We cover key concepts, practical examples, and best practices.

Rust closures and iterators enable expressive functional programming patterns that compile to efficient, zero-cost machine code, letting you write declarative data processing pipelines without runtime overhead.

What You'll Learn

In this tutorial, you'll learn how Rust closures capture their environment, how the <a href="/design-patterns/iterator/">Iterator</a> trait powers lazy data processing, how to chain iterator adapters, and how closures enable higher-order functions for systems programming.

Why It Matters

Systems code often processes streams of data: network packets, file bytes, sensor readings, log lines. Imperative loops with index variables are error-prone and verbose. Rust's iterator combinators express data transformations concisely, with the compiler optimizing them into the same efficient assembly as hand-written loops.

Real-World Use

The Linux bpftrace tool uses Rust iterators to process trace events. Database query engines use iterator chains for filter-map-reduce pipelines. Durga Antivirus Pro's log analysis engine processes millions of log lines per second using iterator adapters with zero allocation overhead.

flowchart LR
    S[Source: Vec, File, Range] --> I[Iterator]
    I --> A1[.filter()]
    A1 --> A2[.map()]
    A2 --> A3[.take()]
    A3 --> C[Consumer: collect, sum, for_each]
    note:  "Lazy until consumed"
â„šī¸ Info

Prerequisites: Traits & Generics and Error Handling. Understanding Rust generics is required.

Closure Basics

Closures are anonymous functions that can capture variables from their environment.

fn main() {
    let factor = 3;
    // Closure that captures factor by reference
    let multiply = |x: i32| x * factor;
    println!("3 * 7 = {}", multiply(7));
    println!("3 * 10 = {}", multiply(10));

    // Closure with type inference
    let add_one = |x| x + 1;
    let nums = vec![1, 2, 3];
    let plus_one: Vec<i32> = nums.into_iter().map(add_one).collect();
    println!("{:?}", plus_one);
}

Expected output:

3 * 7 = 21
3 * 10 = 30
[2, 3, 4]

Capturing Modes

Closures can capture by reference, by mutable reference, or by value depending on how they use captured variables.

fn main() {
    // Capture by reference (Fn)
    let data = vec![1, 2, 3];
    let print = || println!("{:?}", data);
    print(); // borrows data immutably
    println!("Still accessible: {:?}", data);

    // Capture by mutable reference (FnMut)
    let mut counter = 0;
    let mut increment = || {
        counter += 1;
        counter
    };
    println!("Counter: {}", increment());
    println!("Counter: {}", increment());

    // Capture by value (FnOnce)
    let name = String::from("Rust");
    let consume = || {
        let n = name; // moves name into closure
        println!("{}", n);
    };
    consume();
    // println!("{}", name); // ERROR: name moved into closure
}

Expected output:

[1, 2, 3]
Still accessible: [1, 2, 3]
Counter: 1
Counter: 2
Rust

Iterator Basics

The <a href="/design-patterns/iterator/">Iterator</a> trait powers all iteration in Rust. Collections implement IntoIterator.

fn main() {
    // Range iterator
    let sum: i32 = (1..=10).sum();
    println!("Sum 1 to 10: {}", sum);

    // Vector iterator
    let words = vec!["hello", "world", "rust", "closure"];
    for word in words.iter() {
        println!("Word: {}", word);
    }

    // Consuming iterator
    let nums = vec![1, 2, 3, 4, 5];
    let doubled: Vec<i32> = nums.into_iter().map(|x| x * 2).collect();
    println!("Doubled: {:?}", doubled);
}

Expected output:

Sum 1 to 10: 55
Word: hello
Word: world
Word: rust
Word: closure
Doubled: [2, 4, 6, 8, 10]

Iterator Adapters

Adapters transform iterators without consuming them. They are lazy and only execute when consumed.

fn main() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Chain of adapters
    let result: Vec<i32> = data.iter()
        .filter(|&&x| x % 2 == 0)            // keep evens
        .map(|&x| x * x)                      // square them
        .take(3)                               // take first 3
        .collect();                            // consume into Vec

    println!("First 3 even squares: {:?}", result);

    // fold/reduce
    let product = data.iter().fold(1, |acc, x| acc * x);
    println!("Product 1-10: {}", product);

    // any/all
    let all_positive = data.iter().all(|&x| x > 0);
    let any_gt_100 = data.iter().any(|&x| x > 100);
    println!("All positive: {}, Any > 100: {}", all_positive, any_gt_100);
}

Expected output:

First 3 even squares: [4, 16, 36]
Product 1-10: 3628800
All positive: true, Any > 100: false

Custom Iterators

You can implement the <a href="/design-patterns/iterator/">Iterator</a> trait for your own types.

struct Fibonacci {
    current: u64,
    next: u64,
    max: Option<u64>,
}

impl Fibonacci {
    fn new(max: Option<u64>) -> Self {
        Fibonacci { current: 0, next: 1, max }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.current;
        if let Some(max) = self.max {
            if current > max {
                return None;
            }
        }
        self.current = self.next;
        self.next = current + self.next;
        Some(current)
    }
}

fn main() {
    let fib: Vec<u64> = Fibonacci::new(Some(50)).collect();
    println!("Fibonacci up to 50: {:?}", fib);

    let sum: u64 = Fibonacci::new(Some(100)).filter(|x| x % 2 == 0).sum();
    println!("Sum of even fibs <= 100: {}", sum);
}

Expected output:

Fibonacci up to 50: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Sum of even fibs <= 100: 44

Closures and Security

Closures enable security patterns like sandboxing. A closure can capture a limited environment and be passed to untrusted code without exposing the full context. This is used in plugin systems where plugins should only access specific resources.

Durga Antivirus Pro uses closures to define scan callbacks that capture only the relevant file metadata without exposing the full file system API to the scan engine.

Common Mistakes

1. Unnecessary collect() Calls

Chaining collect() between adapter calls forces intermediate allocations. Only collect() at the end of the chain.

2. Capturing Large Data by Value

Moving large structs into closures copies them. Capture by reference when possible, or use Rc/Arc for shared ownership.

3. Confusing iter() and into_iter()

iter() borrows elements (returns references). into_iter() takes ownership. Use iter() when you need to reuse the collection.

4. Not Using Lazy Evaluation

Iterators are lazy. If you do not call a consuming method (collect, sum, for_each), nothing happens. Ensure your chain has a consumer.

5. Modifying Captured Variables Incorrectly

Closures that modify captured variables must be FnMut. You cannot store FnMut closures in types that require Fn.

Practice Questions

1. What are the three closure traits in Rust? Fn (immutable capture), FnMut (mutable capture), FnOnce (consuming capture). The compiler infers the most restrictive trait needed.

2. What does lazy evaluation mean for iterators? Iterator adapters (map, filter, etc.) do not execute until a consuming method is called. This avoids intermediate allocations and allows short-circuiting with take().

3. What is the difference between iter() and into_iter()? iter() borrows each element, returning references. into_iter() consumes the collection and returns owned elements. Use iter() when you need the collection afterward.

4. How do you implement the Iterator trait? Implement <a href="/design-patterns/iterator/">Iterator</a> for your type with type Item and fn next(&mut self) -> Option<Self::Item>. Return None when iteration is complete.

5. Challenge: Create a LogParser iterator that reads lines from a log file, filters by log level, and yields parsed LogEntry structs.

Mini Project: Data Processing Pipeline

struct SensorReading {
    timestamp: u64,
    value: f64,
    unit: String,
}

fn process_readings(readings: Vec<SensorReading>) {
    let avg = readings.iter()
        .filter(|r| r.unit == "temperature")
        .map(|r| r.value)
        .fold(0.0, |sum, v| sum + v)
        / readings.iter().filter(|r| r.unit == "temperature").count() as f64;

    let max = readings.iter()
        .filter(|r| r.unit == "pressure")
        .map(|r| r.value)
        .fold(f64::NEG_INFINITY, |max, v| max.max(v));

    let anomalies: Vec<&SensorReading> = readings.iter()
        .filter(|r| r.value > 100.0)
        .collect();

    println!("Avg temperature: {:.1}", avg);
    println!("Max pressure: {:.1}", max);
    println!("Anomalies (>100): {}", anomalies.len());
}

fn main() {
    let data = vec![
        SensorReading { timestamp: 1, value: 23.5, unit: "temperature".into() },
        SensorReading { timestamp: 2, value: 25.0, unit: "temperature".into() },
        SensorReading { timestamp: 3, value: 1012.0, unit: "pressure".into() },
        SensorReading { timestamp: 4, value: 150.0, unit: "humidity".into() },
    ];
    process_readings(data);
}

FAQ

What is the difference between a closure and a function?

A function is defined with fn and cannot capture its environment. A closure is defined with || and can capture variables from its enclosing scope. Closures are implemented as anonymous structs that hold captured values.

When does Rust infer Fn vs FnMut vs FnOnce?

Rust infers the most restrictive trait based on how the closure uses captured variables. Read-only capture gives Fn, mutable capture gives FnMut, consuming capture gives FnOnce.

Are iterators zero-cost?

Yes. Rust's iterator adapters are zero-cost abstractions. The compiler inlines the adapter chain and optimizes it to the same assembly as a hand-written loop. The collect at the end may allocate, but the pipeline itself has no overhead.

Traits & Generics
Smart Pointers
Concurrency

What's Next

Dive into Smart Pointers for Box, Rc, Arc, and RefCell, then explore Concurrency for threading.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro