Rust: Rust Closures

Last updated: 2026-08-26

A closure is an anonymous function that can capture variables from its surrounding environment. It is called just like a function, but it can "remember" the variables in the scope where it was defined.

If we say that a regular function is like a “set meal on a takeout menu” (where the ingredients come from the parameters), then a closure is like “the ingredients in your own fridge” (where the ingredients come from the environment). A closure can access the variables that were present at the time it was defined—just like taking ingredients from your fridge and cooking with them right away.


1. What You'll Learn



2. Conceptual Diagrams

The following Mermaid diagram illustrates the relationships and differences among the three types of closure capture (Fn, FnMut, and FnOnce):

100%
graph TB
    A["Closure Closure"] --> B["Fn<br/>Immutable Borrowing &T"]
    A --> C["FnMut<br/>Variable Borrowing &mut T"]
    A --> D["FnOnce<br/>Consumer Ownership T"]

    B --> E["let c = || println!(&#34;{ }&#34;, x);"]
    B --> F["Can be called multiple times"]
    B --> G["Environment variables are read-only and immutable"]

    C --> H["let mut c = || x += 1;"]
    C --> I["Can be called multiple times"]
    C --> J["Environment variables can be modified"]

    D --> K["let c = || drop(x);"]
    D --> L["Can only be called once"]
    D --> M["Ownership has been transferred to the closure"]

    B -.-> |Automatic Inference| N["The compiler, based on the closure body<br/>Select the minimum requirement trait"]
    C -.-> N
    D -.-> N
    N --> O["move Mandatory Keywords FnOnce"]


3. The Story Behind the Food Delivery Rating System

(1) The hassle: Writing a separate function for each scoring rule

Mia (Mia) is developing a food delivery rating system. Users can rate restaurants, but the rating rules vary:

At first, she wrote a function for each rule:

RUST
struct Review {
    store: String,
    rating: u32,
}

fn normal_score(r: &Review) -> u32 {
    r.rating
}

fn blogger_score(r: &Review) -> u32 {
    r.rating * 2
}

fn inspector_score(r: &Review) -> u32 {
    if r.rating < 3 { 0 } else { r.rating }
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    for r in &reviews {
        println!("{}: normal={}, blogger={}, inspector={}",
            r.store, normal_score(r), blogger_score(r), inspector_score(r));
    }
}

Aside from differences in their core logic, the three functions have exactly the same function signatures. If the product manager says, “Add another scoring rule for Super Members,” another function would need to be written. Furthermore, these scoring rules cannot be “dynamically generated”—for example, the coefficient in “score × coefficient” must be hard-coded into the function.

(2) More complex requirements: dynamically changing weighting factors

The product manager said: The rating coefficients change daily (depending on promotional activities), and users can choose different rating strategies (scoring, weighting, penalizing low scores). This cannot be achieved using standard functions—function signatures are fixed at compile time and cannot account for external dynamic coefficients.

RUST
// I want to use a function,However, the coefficient is dynamic.
let weight = 1.5;  // Today's Weighting Coefficient
// fn weighted_score(r: &Review) -> u32 {
//     (r.rating as f64 * weight) as u32  // ❌ Compilation Error:weight Outside the function's scope
// }

(3) Solutions for Rust Closures

RUST
struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    // Closure 1: normal score (no capture)
    let normal = |r: &Review| r.rating;

    // Closure 2: weighted score (captures `weight` from environment)
    let weight: f64 = 1.5;
    let weighted = |r: &Review| (r.rating as f64 * weight) as u32;

    // Closure 3: inspector (captures `threshold` from environment)
    let threshold = 3;
    let inspector = |r: &Review| if r.rating < threshold { 0 } else { r.rating };

    for r in &reviews {
        println!("{}: normal={}, weighted={}, inspector={}",
            r.store, normal(r), weighted(r), inspector(r));
    }
}

Output:

TEXT 📖 Display only
Pizzaplace: normal=4, weighted=6, inspector=4
BurgerKing: normal=2, weighted=3, inspector=0
SushiBar: normal=5, weighted=7, inspector=5

A closure |r| r.rating * weight captures external variables weight—something ordinary functions cannot do. weight and threshold can change dynamically (for example, based on user input or configuration files), and the closure automatically captures their current values. This is the core value of closures: a function that can “steal” variables from its surroundings and use them.



4. Core Concepts

(1) Overview of Closure Systems

100%
graph TB
    A[Rust Closures] --> B[Syntax]
    A --> C[Capture Modes]
    A --> D[move Keyword]
    A --> E[As Parameters]
    A --> F[With Iterators]

    B --> B1["|param1, param2| expr"]
    B --> B2["|param| { multiple; statements; }"]

    C --> C1["Fn: immutable borrow (&T)"]
    C --> C2["FnMut: mutable borrow (&mut T)"]
    C --> C3["FnOnce: ownership (T)"]

    D --> D1["let c = move || x;"]
    D --> D2["Forces ownership transfer"]

    E --> E1["fn pointer: fn(T) -> U"]
    E --> E2["Fn trait: impl Fn(T) -> U"]
    E --> E3["FnMut / FnOnce trait bounds"]

    F --> F1["iter().map(|x| x + 1)"]
    F --> F2["iter().filter(|x| x > 0)"]
    F --> F3["Chained: map().filter().collect()"]

(2) Comparison of the Three Capture Modes

Trait Capture Method Number of Calls Can Environment Be Modified? Can Be Called Multiple Times?
FnOnce Consumes ownership (move) Once Can consume environment variables No (ownership has been transferred)
FnMut Variable borrowing Multiple times Environment variables can be modified Yes
Fn Immutable borrow Multiple times Environment variables cannot be modified Yes

(3) Function Pointers fn vs. Closures Fn

Feature Function Pointers fn Closures Fn trait
Does it capture the environment? No—only parameters can be used Yes—environment variables can be captured
Can it be used as a parameter? Yes Yes (more general)
Does it accept closures? No Yes
Type Size fn(T) -> U (pointer size) Different closures have different sizes
Performance Determined Compiler-inlineable
Syntax fn foo(x: i32) -> i32 |x| x + 1

(4) Comparison of Closure Capture Methods

Capture Method Syntax Example Ownership Implications Number of Times the Closure Can Be Called Applicable Scenarios
Immutable Borrow |x| x + var Borrow &T Multiple Read-Only Access to Environment Variables
Variable borrowing |x| { var += 1; ... } Borrowing &mut T Multiple (exclusive) Environment variables must be modified
Move Ownership move |x| x + var Get T Once only (if consumed) The closure must outlive the reference
No capture |x| x + 1 None Multiple Equivalent to a function pointer


5. Examples of Closures

▶ Example 1: Basic Closure Syntax—Food Delivery Ratings (Difficulty ⭐)

Output:

TEXT 📖 Display only
    (triple: <r.rating> -> <result>)
--- Rating Calculations ---
<r.store> (rating: <r.rating>):
  double=<double(r)>, half=<half(r)>

--- Pass Check (min: 3) ---
<r.store>: <if is_pass(r) { "PASS" } else { "FAIL" }>

--- Good Check (min: 4) ---
<r.store>: <if is_good(r) { "GOOD" } else { "OK" }>
<label>: 
<r.store>-><scorer(r)> 
RUST
// ============================================
// Closure basics: syntax, type inference, calling
// ============================================

struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: String::from("Pizzaplace"), rating: 4 },
        Review { store: String::from("BurgerKing"), rating: 2 },
        Review { store: String::from("SushiBar"), rating: 5 },
        Review { store: String::from("NoodleHouse"), rating: 3 },
    ];

    // --- Syntax variation 1: single expression ---
    let double = |r: &Review| r.rating * 2;
    // --- Syntax variation 2: block body ---
    let triple = |r: &Review| {
        let result = r.rating * 3;
        println!("    (triple: {} -> {})", r.rating, result);
        result
    };
    // --- Syntax variation 3: type inference ---
    // Rust infers parameter and return types from usage
    let half = |r| r.rating / 2;  // type inferred as &Review -> u32

    println!("--- Rating Calculations ---");
    for r in &reviews {
        println!("{} (rating: {}):", r.store, r.rating);
        println!("  double={}, half={}", double(r), half(r));
        let _ = triple(r);
    }

    // --- Closures that capture variables ---
    let min_rating = 3;
    let min_rating2 = 4;

    // Capture `min_rating` from the surrounding scope
    let is_pass = |r: &Review| r.rating >= min_rating;
    let is_good = |r: &Review| r.rating >= min_rating2;

    println!("\n--- Pass Check (min: {}) ---", min_rating);
    for r in &reviews {
        println!("{}: {}", r.store, if is_pass(r) { "PASS" } else { "FAIL" });
    }

    println!("\n--- Good Check (min: {}) ---", min_rating2);
    for r in &reviews {
        println!("{}: {}", r.store, if is_good(r) { "GOOD" } else { "OK" });
    }

    // --- Using closures as function arguments ---
    fn check_reviews(reviews: &[Review], label: &str, scorer: impl Fn(&Review) -> u32) {
        print!("{}: ", label);
        for r in reviews {
            print!("{}->{} ", r.store, scorer(r));
        }
        println!();
    }

    let weight = 2;
    let weighted_scorer = |r: &Review| r.rating * weight;

    check_reviews(&reviews, "Weighted(×2)", weighted_scorer);
    check_reviews(&reviews, "Normal", |r| r.rating);
    check_reviews(&reviews, "Bonus", |r| if r.rating >= 4 { r.rating + 1 } else { r.rating });
}

Output:

TEXT 📖 Display only
--- Rating Calculations ---
Pizzaplace (rating: 4):
  double=8, half=2
    (triple: 4 -> 12)
BurgerKing (rating: 2):
  double=4, half=1
    (triple: 2 -> 6)
SushiBar (rating: 5):
  double=10, half=2
    (triple: 5 -> 15)
NoodleHouse (rating: 3):
  double=6, half=1
    (triple: 3 -> 9)

--- Pass Check (min: 3) ---
Pizzaplace: PASS
BurgerKing: FAIL
SushiBar: PASS
NoodleHouse: PASS

--- Good Check (min: 4) ---
Pizzaplace: GOOD
BurgerKing: OK
SushiBar: GOOD
NoodleHouse: OK

--- Weighted(×2) ---
Pizzaplace->8 BurgerKing->4 SushiBar->10 NoodleHouse->6
Normal: Pizzaplace->4 BurgerKing->2 SushiBar->5 NoodleHouse->3
Bonus: Pizzaplace->5 BurgerKing->2 SushiBar->6 NoodleHouse->3

There are three styles of closure syntax: single expression |x| expr, block |x| { stmt; expr }, and type inference |x| x+1 (where the type is inferred from the context). Capturing environment variables is a unique capability of closures: is_pass captures min_rating, and weighted_scorer captures weight. The check_reviews function accepts impl Fn(&Review) -> u32—any closure that implements the Fn trait can be passed to it.


▶ Example 2: Capture Mode—Fn / FnMut / FnOnce (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
=== Fn: Immutable Borrow ===
Scores: [10, 20, 30]
Still accessible: [10, 20, 30]

=== FnMut: Mutable Borrow ===
Counters: [0, 0, 0]

=== FnOnce: Ownership Consumed ===
Consuming: Pizzaplace
Pizzaplace

=== FnOnce with move ===
Sum of data: <sum>
[1, 2, 3, 4, 5]

=== Practical: Closure Type Selection ===
Count (Fn): <count()>
Store (FnMut modified): 
Store length: 0
RUST
// ============================================
// Three capture modes: Fn, FnMut, FnOnce
// ============================================

fn main() {
    println!("=== Fn: Immutable Borrow ===");
    let scores = vec![10, 20, 30];
    // Fn closure: only reads captured variables (immutable borrow)
    let print_scores = || {
        println!("Scores: {:?}", scores);  // &Vec<i32>
    };
    print_scores();  // Can be called multiple times
    print_scores();
    println!("Still accessible: {:?}", scores);  // scores not moved

    println!("\n=== FnMut: Mutable Borrow ===");
    let mut counters = vec![0, 0, 0];
    // FnMut closure: can mutate captured variables
    let mut increment = || {
        for c in &mut counters {
            *c += 1;
        }
    };
    increment();  // Can be called multiple times
    increment();
    increment();
    println!("Counters: {:?}", counters);  // [3, 3, 3]

    println!("\n=== FnOnce: Ownership Consumed ===");
    let name = String::from("Pizzaplace");
    // FnOnce closure: consumes the captured variable
    let consume = || {
        println!("Consuming: {}", name);
        drop(name);  // Explicitly drop (consumes ownership)
    };
    consume();
    // consume();  // ❌ Compile error: closure can only be called once
    // println!("{}", name);  // ❌ Compile error: name was moved

    println!("\n=== FnOnce with move ===");
    let data = vec![1, 2, 3, 4, 5];
    // `move` forces the closure to take ownership of `data`
    let compute = move || {
        let sum: i32 = data.iter().sum();
        println!("Sum of data: {}", sum);
        // data is dropped here at end of closure
    };
    compute();
    // println!("{:?}", data);  // ❌ Compile error: data was moved into the closure
    // compute();  // ❌ Could also fail if the closure consumed data

    println!("\n=== Practical: Closure Type Selection ===");
    let items = vec!["apple", "banana", "cherry"];

    // Fn: read-only
    let count = || items.len();
    println!("Count (Fn): {}", count());

    // FnMut: modify captured variable
    let mut store = String::new();
    let mut append = |item: &str| {
        if !store.is_empty() { store.push_str(", "); }
        store.push_str(item);
    };
    for item in &items {
        append(item);
    }
    println!("Store (FnMut modified): {}", store);
    let store_len = store.len();
    println!("Store length: {}", store_len);
}

Output:

TEXT 📖 Display only
=== Fn: Immutable Borrow ===
Scores: [10, 20, 30]
Scores: [10, 20, 30]
Still accessible: [10, 20, 30]

=== FnMut: Mutable Borrow ===
Counters: [3, 3, 3]

=== FnOnce: Ownership Consumed ===
Consuming: Pizzaplace

=== FnOnce with move ===
Sum of data: 15

=== Practical: Closure Type Selection ===
Count (Fn): 3
Store (FnMut modified): apple, banana, cherry
Store length: 20

The compiler automatically infers which trait to use based on how the closure body accesses captured variables: read-only accessFn, mutable accessFnMut (the closure must be declared with mut), ownership consumptionFnOnce (can only be called once). The move keyword forces ownership to be transferred into the closure—commonly used in multithreading scenarios (thread::spawn requires the 'static lifetime).


▶ Example 3: Closures as Arguments—Function Pointers vs. the Fn Trait (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
--- Function Pointers ---
square(5) via fn ptr: <apply_fn_pointer(square, 5)>
triple(5) via fn ptr: <apply_fn_pointer(triple, 5)>

--- Fn Trait (generic) ---
add_base(5): <apply_fn(add_base, 5)>
square(6) via Fn: <apply_fn(square, 6)>

--- FnMut ---
accumulate(5): <apply_twice(&mut accumulate, 5)>
Final accum: 0

--- FnOnce ---
value:  <x>
describe(42): <apply_once(describe, 42)>

--- Practical: Scoring Strategies ---
<scores>: <label>
RUST
// ============================================
// Closures as parameters: fn pointer vs Fn trait
// ============================================

// --- Version 1: Accepts ONLY function pointers ---
// fn(T) -> U is a function pointer type (no environment capture)
fn apply_fn_pointer(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

// --- Version 2: Accepts ANY Fn trait (including closures) ---
// impl Fn(T) -> U accepts both fn pointers and closures
fn apply_fn<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

// --- Version 3: FnMut parameter ---
fn apply_twice<F>(mut f: F, x: i32) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(f(x))
}

// --- Version 4: FnOnce parameter ---
fn apply_once<F>(f: F, x: i32) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(x)
}

// A regular function (can be used as fn pointer)
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    // --- Regular functions as fn pointers ---
    println!("--- Function Pointers ---");
    // `square` is a function, can be passed as fn(i32) -> i32
    println!("square(5) via fn ptr: {}", apply_fn_pointer(square, 5));

    // Annotated closure (no capture) can be coerced to fn pointer
    let triple = |x: i32| x * 3;
    println!("triple(5) via fn ptr: {}", apply_fn_pointer(triple, 5));

    // --- Closures with Fn trait ---
    println!("\n--- Fn Trait (generic) ---");
    let base = 10;
    // This closure captures `base`, so it CANNOT be a fn pointer
    let add_base = |x: i32| x + base;
    println!("add_base(5): {}", apply_fn(add_base, 5));

    // Both fn pointers and closures work with Fn trait
    println!("square(6) via Fn: {}", apply_fn(square, 6));

    // --- FnMut in action ---
    println!("\n--- FnMut ---");
    let mut accum = 0;
    let mut accumulate = |x: i32| {
        accum += x;
        accum
    };
    println!("accumulate(5): {}", apply_twice(&mut accumulate, 5));
    // After apply_twice: accum = 5 + 5 = 10, then 10 + 5 = 15
    println!("Final accum: {}", accum);

    // --- FnOnce in action ---
    println!("\n--- FnOnce ---");
    let owned = String::from("value: ");
    let describe = |x: i32| {
        println!("{} {}", owned, x);
        x
    };
    println!("describe(42): {}", apply_once(describe, 42));
    // describe was consumed (FnOnce), cannot call again

    // --- Practical: scoring system with different strategies ---
    println!("\n--- Practical: Scoring Strategies ---");

    fn run_strategy<F>(reviews: &[u32], label: &str, strategy: F)
    where
        F: Fn(u32) -> u32,
    {
        let scores: Vec<u32> = reviews.iter().map(|&r| strategy(r)).collect();
        println!("{}: {:?}", label, scores);
    }

    let ratings = [4, 2, 5, 3, 1];
    let bonus_threshold = 4;
    let bonus_points = 1;

    // Different scoring strategies, all as closures
    run_strategy(&ratings, "Normal", |r| r);
    run_strategy(&ratings, "Double", |r| r * 2);
    run_strategy(&ratings, "Bonus", |r| if r >= bonus_threshold { r + bonus_points } else { r });
    run_strategy(&ratings, "Penalty", |r| if r < 3 { 0 } else { r });
}

Output:

TEXT 📖 Display only
--- Function Pointers ---
square(5) via fn ptr: 25
triple(5) via fn ptr: 15

--- Fn Trait (generic) ---
add_base(5): 15
square(6) via Fn: 36

--- FnMut ---
accumulate(5): 15
Final accum: 15

--- FnOnce ---
value:  42
describe(42): 42

--- Practical: Scoring Strategies ---
Normal: [4, 2, 5, 3, 1]
Double: [8, 4, 10, 6, 2]
Bonus: [5, 2, 6, 3, 1]
Penalty: [4, 0, 5, 3, 0]

Function pointers fn(i32) -> i32 can only accept ordinary functions and closures that do not capture the environment. The Fn trait (generic) can accept any closure (including those that capture the environment). Selection Rules: Use fn pointers for FFI or when you need to store a fixed function type; accept Fn/FnMut/FnOnce traits for scenarios requiring closures that capture the environment. run_strategy functions accept impl Fn(u32) -> u32—four different scoring strategies—as closures, including Bonus strategies that capture bonus_threshold and bonus_points.


▶ Example 4: Combining Closures and Iterators—map / filter / collect (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
--- map: store names uppercase ---
<store_names>

--- filter: orders with rating >= 4 ---
<o>

--- filter + map: good store names ---
<s>

--- map + filter: average rating per item ---
<s>

--- Advanced: dynamic threshold filtering ---
<s>

--- Flat items from high-rated stores ---
Recommended: <recommended_items>

--- Custom filter: weighted score ---
<o.rating>: raw=<weighted>, weighted=<o.store>, pass=<weighted >= min_score>

--- Aggregate checks ---
Orders with rating >= 4: <high_rated_count>
Has any bad order (rating <= 1): <has_bad_order>
All orders rated at least 1: <all_rated>
RUST
// ============================================
// Closures + Iterator combinators: map, filter, collect
// ============================================

#[derive(Debug)]
struct Order {
    store: String,
    items: Vec<String>,
    rating: u32,
}

fn main() {
    let orders = vec![
        Order {
            store: String::from("Pizzaplace"),
            items: vec!["Margherita".into(), "Cola".into()],
            rating: 4,
        },
        Order {
            store: String::from("BurgerKing"),
            items: vec!["Whopper".into(), "Fries".into(), "Shake".into()],
            rating: 2,
        },
        Order {
            store: String::from("SushiBar"),
            items: vec!["Salmon".into(), "Tuna".into()],
            rating: 5,
        },
        Order {
            store: String::from("NoodleHouse"),
            items: vec!["Ramen".into(), "Gyoza".into(), "Tea".into()],
            rating: 3,
        },
    ];

    // --- map: transform each element ---
    println!("--- map: store names uppercase ---");
    let store_names: Vec<String> = orders
        .iter()
        .map(|o| o.store.to_uppercase())
        .collect();
    println!("{:?}", store_names);

    // --- filter: keep elements matching a condition ---
    println!("\n--- filter: orders with rating >= 4 ---");
    let good_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .collect();
    for o in &good_orders {
        println!("{:?}", o);
    }

    // --- filter + map: chained ---
    println!("\n--- filter + map: good store names ---");
    let good_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .map(|o| format!("★ {} (rating: {})", o.store, o.rating))
        .collect();
    for s in &good_stores {
        println!("{}", s);
    }

    // --- map + filter: compute and refine ---
    println!("\n--- map + filter: average rating per item ---");
    let avg_ratings: Vec<String> = orders
        .iter()
        .map(|o| {
            // Compute average rating per item
            let item_count = o.items.len() as f64;
            let avg = o.rating as f64 / item_count;
            (o.store.clone(), avg, o.items.len())
        })
        .filter(|(_, avg, _)| *avg > 1.5)  // Only stores with high per-item rating
        .map(|(store, avg, count)| format!("{}: {:.2}/item ({} items)", store, avg, count))
        .collect();
    for s in &avg_ratings {
        println!("{}", s);
    }

    // --- Advanced: closures capturing external state ---
    println!("\n--- Advanced: dynamic threshold filtering ---");
    let min_rating = 3;
    let category = "Premium";

    let premium_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= min_rating)
        .map(|o| format!("[{}] {} (rating: {})", category, o.store, o.rating))
        .collect();

    for s in &premium_stores {
        println!("{}", s);
    }

    // --- All items from high-rated stores ---
    println!("\n--- Flat items from high-rated stores ---");
    let threshold = 3;
    let recommended_items: Vec<&String> = orders
        .iter()
        .filter(|o| o.rating > threshold)
        .flat_map(|o| o.items.iter())
        .collect();
    println!("Recommended: {:?}", recommended_items);

    // --- Custom scoring with filter ---
    println!("\n--- Custom filter: weighted score ---");
    let weight = 1.2;
    let min_score = 4.0;

    let top_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| (o.rating as f64 * weight) >= min_score)
        .collect();
    for o in &top_orders {
        let weighted = o.rating as f64 * weight;
        println!("{}: raw={}, weighted={:.1}, pass={}", o.store, o.rating, weighted, weighted >= min_score);
    }

    // --- count, any, all ---
    println!("\n--- Aggregate checks ---");
    let high_rated_count = orders.iter().filter(|o| o.rating >= 4).count();
    println!("Orders with rating >= 4: {}", high_rated_count);

    let has_bad_order = orders.iter().any(|o| o.rating <= 1);
    println!("Has any bad order (rating <= 1): {}", has_bad_order);

    let all_rated = orders.iter().all(|o| o.rating >= 1);
    println!("All orders rated at least 1: {}", all_rated);
}

Output:

TEXT 📖 Display only
--- map: store names uppercase ---
["PIZZAPLACE", "BURGERKING", "SUSHIBAR", "NOODLEHOUSE"]

--- filter: orders with rating >= 4 ---
Order { store: "Pizzaplace", items: ["Margherita", "Cola"], rating: 4 }
Order { store: "SushiBar", items: ["Salmon", "Tuna"], rating: 5 }

--- filter + map: good store names ---
★ Pizzaplace (rating: 4)
★ SushiBar (rating: 5)

--- map + filter: average rating per item ---
Pizzaplace: 2.00/item (2 items)
SushiBar: 2.50/item (2 items)

--- Advanced: dynamic threshold filtering ---
[Premium] Pizzaplace (rating: 4)
[Premium] SushiBar (rating: 5)
[Premium] NoodleHouse (rating: 3)

--- Flat items from high-rated stores ---
Recommended: ["Margherita", "Cola", "Salmon", "Tuna"]

--- Custom filter: weighted score ---
Pizzaplace: raw=4, weighted=4.8, pass=true
SushiBar: raw=5, weighted=6.0, pass=true

--- Aggregate checks ---
Orders with rating >= 4: 2
Has any bad order (rating <= 1): false
All orders rated at least 1: true

Iterator combinators are one of the most powerful applications of closures: map (transforming each element), filter (retaining elements based on conditions), flat_map (flattening nested iterators), any/all (aggregation checks), count (counting). The value of closures here lies in their ability to capture external dynamic values (such as min_rating, weight, and category), decoupling the data-processing logic from the configuration. Chained calls iter().filter().map().collect() are among the most expressive idioms in Rust.


▶ Example 5: Comprehensive Exercise—Implementing a Configuration-Driven Data Pipeline Using Closures (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
Filter+ After the bonus points are added: <result>
Double+ Upper Limit Filtering: <adjusted>
Has values greater than 150: <has_high>, All > 100: <all_above_100>
RUST
// ============================================
// Comprehensive Example: Closure Capture + Higher-Order Functions + Iterator Chain
// ============================================

struct DataPipeline<'a, T> {
    data: Vec<T>,
    filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
    transforms: Vec<Box<dyn Fn(T) -> T + 'a>>,
}

impl<'a, T: Clone + 'a> DataPipeline<'a, T> {
    fn new(data: Vec<T>) -> Self {
        DataPipeline { data, filters: Vec::new(), transforms: Vec::new() }
    }

    fn filter<F: Fn(&T) -> bool + 'a>(mut self, f: F) -> Self {
        self.filters.push(Box::new(f));
        self
    }

    fn map<F: Fn(T) -> T + 'a>(mut self, f: F) -> Self {
        self.transforms.push(Box::new(f));
        self
    }

    fn execute(self) -> Vec<T> {
        let mut result = self.data;
        for f in &self.filters {
            result.retain(f);
        }
        for t in &self.transforms {
            result = result.into_iter().map(t).collect();
        }
        result
    }
}

fn main() {
    let min_score = 60;
    let bonus = 10;
    let max_score = 100;

    let pipeline = DataPipeline::new(vec![85, 42, 95, 58, 73, 30, 88])
        .filter(move |&&x| x >= min_score)
        .filter(|&&x| x < max_score)
        .map(move |x| x + bonus);

    let result = pipeline.execute();
    println!("Filter+ After the bonus points are added: {:?}", result);

    let multiplier = 2;
    let adjusted: Vec<i32> = result.into_iter()
        .map(|x| x * multiplier)
        .filter(|x| *x < 200)
        .collect();
    println!("Double+ Upper Limit Filtering: {:?}", adjusted);

    let threshold = 150;
    let has_high = adjusted.iter().any(|&x| x > threshold);
    let all_above_100 = adjusted.iter().all(|&x| x > 100);
    println!("Has values greater than {}: {}, All > 100: {}", threshold, has_high, all_above_100);
}

Output:

TEXT 📖 Display only
Filter+ After the bonus points are added: [95, 83, 98]
Double+ Upper Limit Filtering: [190, 166, 196]
Has values greater than 150: true, All > 100: true

DataPipeline uses Box<dyn Fn> to store closures, move captures external configuration values (min_score, bonus), and chained calls to filter().map() build the processing pipeline. Closures make it possible to "separate configuration from logic."


❓ FAQ

Q What is the fundamental difference between closures and regular functions?
A Closures can capture variables from their surrounding scope; regular functions cannot.
Q How does the compiler choose between Fn, FnMut, and FnOnce?
A The compiler automatically infers based on how the closure body accesses captured variables.
Q When should you use the move keyword?
A When you need the closure to own the captured variables, especially in multithreading scenarios.
Q Can function pointers fn(T) -> U and closure trait Fn(T) -> U be used interchangeably?
A Not fully interchangeable. Function pointers cannot capture the environment; closures can.
Q When using closures as parameters, should I choose impl Fn, Box<dyn Fn>, or generic F: Fn?
A Generic constraint F: Fn has the best performance (static dispatch), Box<dyn Fn> is the most flexible (dynamic dispatch), and impl Fn is syntactic sugar for generics.
Q How does a closure perform? Is it slower than a regular function?
A Closures that do not capture the environment perform exactly the same as regular functions. Closures that capture the environment have zero or near-zero overhead.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: There is a Vec<i32> that contains [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Write a closure that captures the variable threshold (set to 5), use filter to filter out numbers greater than threshold, use map to double them, and finally use collect to add them to a new Vec and print the result.

  2. Difficulty ⭐⭐: Write a function fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32 that applies a transformation function to each element. In the main function, create three closures: |x| x * 2 (double), an addition closure that captures the variable add, and a truncation closure that captures the variable max_val (returns max_val if the value exceeds max_val). Call transform for each one and print the results.

  3. Difficulty ⭐⭐⭐: There is a struct named struct Product { name: String, price: f64, category: String }. Create 6 product instances and store them in a vector. Write a function fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64 that first filters by category using filter, then adjusts the price using map, and finally sorts the products by their adjusted prices and prints the top 3 most expensive products. In the main function, test this at least twice using different filtering and price-adjustment strategies.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏