Rust: Rust 闭包(Closures):匿名函数捕获环境
最后更新:2026-08-26
闭包(closure)是可以捕获周围环境变量的匿名函数。它像函数一样被调用,但能"记住"定义时所在作用域的变量。
如果说普通函数是"外卖菜单上的固定套餐"(食材从参数来),那闭包就是"你自己冰箱里的菜"(食材从环境来)。闭包可以访问定义时周围的变量——就像从冰箱里拿食材直接下厨。
1. 你将学到
- 闭包语法
|params| expr和|params| { block }的写法 - 三种捕获模式:
Fn(不可变借用)、FnMut(可变借用)、FnOnce(消费所有权) move关键字——强制将捕获的变量所有权移入闭包- 闭包作为函数参数:函数指针
fn与Fntrait 的区别 - 闭包与迭代器组合:
map、filter、collect链式调用 - 闭包在实际场景中的应用模式
2. 概念图解
以下 Mermaid 图展示闭包三种捕获方式(Fn / FnMut / FnOnce)的关系与区别:
graph TB
A["闭包 Closure"] --> B["Fn<br/>不可变借用 &T"]
A --> C["FnMut<br/>可变借用 &mut T"]
A --> D["FnOnce<br/>消费所有权 T"]
B --> E["let c = || println!("{ }", x);"]
B --> F["可多次调用"]
B --> G["环境变量只读不可变"]
C --> H["let mut c = || x += 1;"]
C --> I["可多次调用"]
C --> J["可修改环境变量"]
D --> K["let c = || drop(x);"]
D --> L["只能调用一次"]
D --> M["所有权已移入闭包"]
B -.-> |自动推断| N["编译器根据闭包体<br/>选择最低要求的 trait"]
C -.-> N
D -.-> N
N --> O["move 关键字强制 FnOnce"]
3. 外卖评分系统的故事
(1) 痛苦:为每种评分规则写单独的函数
Mia (Mia) 在开发一个外卖评分系统。用户可以对店铺评分,但评分规则各不相同:
- 普通用户:直接给 1-5 星的评分
- 美食博主:评分 × 2(权重更高)
- 卫生检查员:分数低于 3 的标记为"需整改"
最开始她为每种规则写一个函数:
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));
}
}
三个函数除了核心逻辑不同,函数签名完全一样。如果产品经理说"再加一个超级会员评分规则"又要多写一个函数。而且这些评分规则不能"动态生成"——比如"评分 × 系数"中的系数必须是写死在函数里的。
(2) 更复杂的需求:评分系数动态变化
产品经理说:评分系数每天变化(根据促销活动),而且用户可以选择不同的评分策略(打分、加权、惩罚低分)。用普通函数做不到——函数签名在编译时固定,无法捕获外部的动态系数。
// 想用函数,但系数是动态的
let weight = 1.5; // 今天的权重系数
// fn weighted_score(r: &Review) -> u32 {
// (r.rating as f64 * weight) as u32 // ❌ 编译错误:weight 不在函数作用域内
// }
(3) 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));
}
}
输出:
Pizzaplace: normal=4, weighted=6, inspector=4
BurgerKing: normal=2, weighted=3, inspector=0
SushiBar: normal=5, weighted=7, inspector=5
闭包
|r| r.rating * weight捕获了外部变量weight——这是普通函数做不到的。weight和threshold可以动态变化(比如来自用户输入或配置文件),闭包自动捕获当前值。这就是闭包的核心价值:一个可以从周围环境"偷"变量来用的函数。
4. 核心概念
(1) 闭包体系总览
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) 三种捕获模式对比
| Trait | 捕获方式 | 调用次数 | 是否可修改环境 | 是否可以多次调用 |
|---|---|---|---|---|
FnOnce |
消费所有权(move) | 一次 | 可消费环境变量 | 否(所有权已转移) |
FnMut |
可变借用 | 多次 | 可以修改环境变量 | 是 |
Fn |
不可变借用 | 多次 | 不能修改环境变量 | 是 |
(3) 函数指针 fn vs 闭包 Fn
| 特性 | 函数指针 fn |
闭包 Fn trait |
|---|---|---|
| 是否捕获环境 | 否——只能使用参数 | 是——可以捕获环境变量 |
| 能否作为参数 | 能 | 能(更通用) |
| 能否接受闭包 | 不能 | 能 |
| 类型大小 | fn(T) -> U(指针大小) |
不同闭包有不同大小 |
| 性能 | 确定 | 编译器可内联 |
| 语法 | fn foo(x: i32) -> i32 |
|x| x + 1 |
(4) 闭包捕获方式对比
| 捕获方式 | 语法示例 | 所有权影响 | 闭包可调用次数 | 适用场景 |
|---|---|---|---|---|
| 不可变借用 | |x| x + var |
借用 &T |
多次 | 只读访问环境变量 |
| 可变借用 | |x| { var += 1; ... } |
借用 &mut T |
多次(独占) | 需修改环境变量 |
| 移动所有权 | move |x| x + var |
获取 T |
仅一次(若消耗) | 闭包需比引用活得更久 |
| 无捕获 | |x| x + 1 |
无 | 多次 | 等价于函数指针 |
5. 闭包示例
▶ 示例 1:闭包基础语法——外卖评分(难度 ⭐)
// ============================================
// 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 });
}
输出:
--- 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
闭包语法有三种风格:单表达式
|x| expr、块体|x| { stmt; expr }、类型推断|x| x+1(类型从上下文推断)。捕获环境变量是闭包的独特能力:is_pass捕获了min_rating,weighted_scorer捕获了weight。check_reviews函数接受impl Fn(&Review) -> u32——任何实现了Fntrait 的闭包都可以传入。
▶ 示例 2:捕获模式——Fn / FnMut / FnOnce(难度 ⭐⭐)
// ============================================
// 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);
}
输出:
=== 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
编译器根据闭包体对捕获变量的操作方式自动推断使用哪个 trait:只读访问 ➔
Fn,可变访问 ➔FnMut(必须用mut声明闭包),消费所有权 ➔FnOnce(只能调用一次)。move关键字强制将所有权移入闭包——常用于多线程场景(thread::spawn需要'static生命周期)。
▶ 示例 3:闭包作为参数——函数指针 vs Fn trait(难度 ⭐⭐)
// ============================================
// 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 });
}
输出:
--- 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]
函数指针
fn(i32) -> i32只能接受普通函数和不捕获环境的闭包。Fntrait(泛型)可以接受任何闭包(包括捕获环境的)。选择规则:接受fn指针用于 FFI 或需要存储固定的函数类型;接受Fn/FnMut/FnOncetrait 用于需要闭包捕获环境的场景。run_strategy函数接受impl Fn(u32) -> u32——四种不同的评分策略作为闭包传入,包括捕获了bonus_threshold和bonus_points的 Bonus 策略。
▶ 示例 4:闭包与迭代器组合——map / filter / collect(难度 ⭐⭐⭐)
// ============================================
// 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);
}
输出:
--- 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
迭代器组合子是闭包最强大的应用场景之一:
map(转换每个元素)、filter(按条件保留元素)、flat_map(展平嵌套迭代器)、any/all(聚合检查)、count(计数)。闭包在这里的价值在于:可以捕获外部动态值(如min_rating、weight、category),让数据处理的逻辑与配置解耦。链式调用iter().filter().map().collect()是 Rust 中最具表现力的惯用法。
▶ 示例 5:综合练习——闭包实现配置驱动的数据管道(难度 ⭐⭐⭐)
// ============================================
// 综合示例:闭包捕获 + 高阶函数 + 迭代器链
// ============================================
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!("筛选+加分后: {:?}", result);
let multiplier = 2;
let adjusted: Vec<i32> = result.into_iter()
.map(|x| x * multiplier)
.filter(|x| *x < 200)
.collect();
println!("翻倍+上限过滤: {:?}", adjusted);
let threshold = 150;
let has_high = adjusted.iter().any(|&x| x > threshold);
let all_above_100 = adjusted.iter().all(|&x| x > 100);
println!("有超过{}的: {}, 全部>100: {}", threshold, has_high, all_above_100);
}
输出:
筛选+加分后: [95, 83, 98]
翻倍+上限过滤: [190, 166, 196]
有超过150的: true, 全部>100: true
DataPipeline用Box<dyn Fn>存储闭包,move捕获外部配置值(min_score、bonus),链式调用filter().map()构建处理管道。闭包让"配置与逻辑分离"成为可能。
❓ 常见问题
Fn、FnMut、FnOnce 编译器是怎么选的?move 关键字?fn(T) -> U 和闭包 trait Fn(T) -> U 可以互相替代吗?impl Fn、Box<dyn Fn>、泛型 F: Fn 选哪个?F: Fn 性能最好(静态分发),Box<dyn Fn> 最灵活(动态分发),impl Fn 是泛型的语法糖。📖 小节
- 闭包语法
|params| expr或|params| { block },参数和返回值类型通常可以推断 - 三种捕获模式:
Fn(不可变借用,可多次调用)、FnMut(可变借用,可多次调用)、FnOnce(消费所有权,只能调用一次) move关键字强制将捕获变量的所有权移入闭包——多线程编程的标配- 函数参数可接受闭包:
fn指针(无捕获)或Fn/FnMut/FnOncetrait(通用) - 闭包 + 迭代器组合(
map、filter、flat_map、any、all)是 Rust 最强大的惯用模式 - Rust 闭包是零成本抽象——编译时内联,运行时零开销
📝 作业
-
难度 ⭐:有一个
Vec<i32>包含[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。写一个闭包捕获变量threshold(设为 5),用filter筛选出大于threshold的数,用map将它们翻倍,最后collect到新的 Vec 中并打印。 -
难度 ⭐⭐:写一个函数
fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32,对每个元素应用变换函数。在 main 中创建三个闭包:|x| x * 2(翻倍)、捕获变量add的加法闭包、捕获变量max_val的截断闭包(如果超过max_val就返回max_val)。分别调用transform并打印结果。 -
难度 ⭐⭐⭐:有一个结构体
struct Product { name: String, price: f64, category: String }。创建 6 个产品实例放入 Vec。写一个函数fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64,先用filter筛选类别,再用map调整价格,最后按调整后的价格排序并打印前 3 个最贵的产品。在 main 中用不同的筛选和调价策略测试至少 2 次。