闭包就是能"记住"外部变量的匿名函数。
假设你在写购物车功能,不同会员等级有不同折扣。你会怎么写?
注:本文代码示例是为了方便叙事,做了很多简化,不能与实际业务场景关联。
没有闭包:写到手软
fn apply_discount_80(price: f64) -> f64 {
price * 0.8 // 钻石会员 8 折
}
fn apply_discount_85(price: f64) -> f64 {
price * 0.85 // 黄金会员 85 折
}
fn apply_discount_90(price: f64) -> f64 {
price * 0.9 // 白银会员 9 折
}
三个几乎一样的函数,只是数字不同。要是再来几个会员等级,还得继续写。
用闭包:一行搞定
let discount_rate = 0.85; // 黄金会员折扣
// 闭包"记住"了折扣率
let apply_discount = |price: f64| price * discount_rate;
println!("商品 A: {}", apply_discount(100.0)); // 85.0
println!("商品 B: {}", apply_discount(200.0)); // 170.0
闭包 |price| price * discount_rate 能"看到"外部的 discount_rate。语法是 |参数| 表达式。
闭包怎么"记住"变量?
上面的例子里,闭包只是读取了 discount_rate。但如果闭包要修改外部变量呢?或者要消耗它呢?
Rust 根据闭包对外部变量的操作,自动选择三种不同的"记忆"方式:
1. 只看不动 → Fn
let discount_rate = 0.85;
let apply_discount = |price: f64| price * discount_rate;
// 可以多次调用
println!("{}", apply_discount(100.0));
println!("{}", apply_discount(200.0));
闭包只读取 discount_rate,不修改。可以随便调用多少次。
2. 要改它 → FnMut
let mut total = 0.0;
let mut add_to_cart = |price: f64| {
total += price; // 修改 total
};
add_to_cart(100.0);
add_to_cart(200.0);
println!("总价: {}", total); // 300.0
闭包修改了 total。注意 total 和闭包都要声明为 mut。
3. 用完就没 → FnOnce
let items = vec!["键盘", "鼠标"];
let submit = || {
println!("{:?}", items); // items 被移动进闭包
};
submit();
// submit(); // 错误!只能调用一次
闭包拿走了 items 的所有权,只能调用一次。
对比
| 闭包做了什么 | trait | 能调几次 |
|---|---|---|
| 只读 | Fn | 多次 |
| 修改 | FnMut | 多次 |
| 消耗 | FnOnce | 一次 |
编译器会自动判断用哪个,你不需要手动指定。
move 关键字
多线程时,闭包需要拿走变量的所有权:
use std::thread;
let items = vec!["键盘", "鼠标"];
let handle = thread::spawn(move || {
println!("{:?}", items); // move 强制获取所有权
});
handle.join().unwrap();
为什么?因为新线程可能活得比当前函数长。如果只是借用,函数结束后变量就没了。
总结
闭包就这么回事:能记住外部变量的匿名函数。
记住的方式有三种:只看(Fn)、要改(FnMut)、用完就没(FnOnce)。编译器会自动选,你不用管。
性能?放心用,编译器会优化。只有在多线程时需要注意加 move。