Rust 闭包
闭包语法、环境捕获、Fn/FnMut/FnOnce 家族与在迭代器/线程中的使用边界。
#concept
#growing
#dev
#rust
[!info] related notes
Rust 闭包
这个概念为什么出现
要把一小段逻辑就地传给 map/thread::spawn,并捕获周围变量。
闭包是带环境的匿名函数。
[!abstract] 一句话理解 闭包
|args| expr可捕获环境;由闭包体如何使用捕获值决定实现哪些Fn/FnMut/FnOnce,决定能调用几次以及能否修改/搬走环境。
最小可运行示例
场景:价格引擎——捕获折扣率的闭包批量映射
促销活动有统一折扣率;对价格列表用闭包映射折后价。
// 业务意图:用捕获的折扣率批量计算折后价。
// 教学点:闭包捕获;map;move 闭包预告。
fn main() {
let discount_pct = 85u32;
let prices = [1000u32, 2000, 500];
let discounted: Vec<u64> = prices
.iter()
.map(|p| u64::from(*p) * u64::from(discount_pct) / 100)
.collect();
println!("{discounted:?}");
}
建议运行:cargo run
期望输出:
[850, 1700, 425]
结合场景再看三个关注点
- 闭包自动捕获
discount_pct - 迭代器 API 依赖闭包
- 线程场景常要
move闭包 转移所有权
核心概念与准确模型
| Trait | 能力 |
|---|---|
FnOnce | 调用按值接收闭包,可移出捕获值;所有闭包都实现 |
FnMut | 调用借用 &mut self,可修改捕获状态但不移出非 Copy 捕获值 |
Fn | 调用借用 &self,不需独占修改或移出捕获状态 |
语法:
let add = |a, b| a + b;
assert_eq!(add(1_i32, 2_i32), 3); // 调用提供推断信息
let add = |a: i32, b: i32| -> i32 { a + b };
Fn: FnMut: FnOnce 是能力包含关系。只知道 F: FnOnce 的调用者最多能消耗调用一次,不代表所有实现 FnOnce 的闭包都只能调用一次。
move 强制按值捕获(非 Copy 值移动、Copy 值复制),不决定调用 trait;move 捕获 String 后仅读取仍可实现 Fn。移动一个短生命周期引用不会延长其借用,也不会自动满足 static。
设计动机
- 就地定制算法
- 与所有权一致的捕获规则
- 零成本:常内联单态化
边界与误区
- 生命周期:闭包不能返回指向短暂捕获的引用
- 类型通常不可写出,靠推断与
impl Fn - 递归闭包麻烦
[!warning] 常见误区:在 thread::spawn 里不 move 导致借用寿命不够 普通 spawn 要求闭包及返回值为 Send + static;move 仅转移捕获,不能修复其中短借用或 Rc。需要借用调用栈数据时可考虑 thread::scope。
工程实践
- 短逻辑闭包,长逻辑命名函数
- 明确
move意图 - 作为参数时写
impl Fn...或泛型 bound - 测试可注入闭包做依赖替换
本节总结
- 闭包 = 函数 + 环境
- Fn 家族描述调用能力
- 迭代器/并发大量依赖
自测题
FnOnce何时出现?move关键字作用?
参考答案
- 所有闭包都实现 FnOnce;若调用时移出非 Copy 捕获值,则通常只实现 FnOnce,不能反复调用。
- 强制按值捕获环境,常用于线程。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Closures | 官方书 | 闭包 |
| std::ops::Fn | 标准库 | Fn 家族 |