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
- How to write closure syntax
|params| exprand|params| { block } - Three capture modes:
Fn(immutable borrowing),FnMut(mutable borrowing),FnOnce(ownership consumption) moveKeyword—Forces the ownership of captured variables to be transferred to the closure- Closures as Function Arguments: The Difference Between Function Pointers
fnand theFnTrait - Combining closures and iterators:
map,filter,collectchained calls - Patterns for Using Closures in Real-World Scenarios
2. Conceptual Diagrams
The following Mermaid diagram illustrates the relationships and differences among the three types of closure capture (Fn, FnMut, and FnOnce):
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!("{ }", 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:
- Regular users: Rate directly with 1–5 stars
- Food blogger: Rating × 2 (higher weighting)
- Health Inspector: Scores below 3 are marked as "Needs Correction"
At first, she wrote a function for each rule:
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.
// 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
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:
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 * weightcaptures external variablesweight—something ordinary functions cannot do.weightandthresholdcan 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
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:
(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)>
// ============================================
// 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:
--- 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_passcapturesmin_rating, andweighted_scorercapturesweight. Thecheck_reviewsfunction acceptsimpl Fn(&Review) -> u32—any closure that implements theFntrait can be passed to it.
▶ Example 2: Capture Mode—Fn / FnMut / FnOnce (Difficulty ⭐⭐)
Output:
=== 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
// ============================================
// 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:
=== 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 access ➔
Fn, mutable access ➔FnMut(the closure must be declared withmut), ownership consumption ➔FnOnce(can only be called once). Themovekeyword forces ownership to be transferred into the closure—commonly used in multithreading scenarios (thread::spawnrequires the'staticlifetime).
▶ Example 3: Closures as Arguments—Function Pointers vs. the Fn Trait (Difficulty ⭐⭐)
Output:
--- 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>
// ============================================
// 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:
--- 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) -> i32can only accept ordinary functions and closures that do not capture the environment. TheFntrait (generic) can accept any closure (including those that capture the environment). Selection Rules: Usefnpointers for FFI or when you need to store a fixed function type; acceptFn/FnMut/FnOncetraits for scenarios requiring closures that capture the environment.run_strategyfunctions acceptimpl Fn(u32) -> u32—four different scoring strategies—as closures, including Bonus strategies that capturebonus_thresholdandbonus_points.
▶ Example 4: Combining Closures and Iterators—map / filter / collect (Difficulty ⭐⭐⭐)
Output:
--- 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>
// ============================================
// 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:
--- 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 asmin_rating,weight, andcategory), decoupling the data-processing logic from the configuration. Chained callsiter().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:
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>
// ============================================
// 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:
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
DataPipelineusesBox<dyn Fn>to store closures,movecaptures external configuration values (min_score,bonus), and chained calls tofilter().map()build the processing pipeline. Closures make it possible to "separate configuration from logic."
❓ FAQ
move keyword?fn(T) -> U and closure trait Fn(T) -> U be used interchangeably?impl Fn, Box<dyn Fn>, or generic F: Fn?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.📖 Summary
- Closure syntax
|params| expror|params| { block }; parameter and return types are usually inferred - Three capture modes:
Fn(immutable borrowing, can be called multiple times),FnMut(mutable borrowing, can be called multiple times),FnOnce(consumes ownership, can only be called once) moveThe keyword forces the ownership of captured variables to be transferred to the closure—a standard practice in multithreaded programming- Function parameters can accept closures:
fnpointers (no capture) orFn/FnMut/FnOncetraits (generic) - The closure-iterator combination (
map,filter,flat_map,any,all) is one of Rust’s most powerful idioms - Rust closures are zero-cost abstractions—inlined at compile time, with zero runtime overhead
📝 Exercises
-
Difficulty ⭐: There is a
Vec<i32>that contains[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Write a closure that captures the variablethreshold(set to 5), usefilterto filter out numbers greater thanthreshold, usemapto double them, and finally usecollectto add them to a new Vec and print the result. -
Difficulty ⭐⭐: Write a function
fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32that applies a transformation function to each element. In themainfunction, create three closures:|x| x * 2(double), an addition closure that captures the variableadd, and a truncation closure that captures the variablemax_val(returnsmax_valif the value exceedsmax_val). Calltransformfor each one and print the results. -
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 functionfn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64that first filters by category usingfilter, then adjusts the price usingmap, and finally sorts the products by their adjusted prices and prints the top 3 most expensive products. In themainfunction, test this at least twice using different filtering and price-adjustment strategies.