一个例子搞懂 Rust 闭包

16 阅读2分钟

闭包就是能"记住"外部变量的匿名函数。

假设你在写购物车功能,不同会员等级有不同折扣。你会怎么写?

注:本文代码示例是为了方便叙事,做了很多简化,不能与实际业务场景关联。

没有闭包:写到手软

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