在 Rust 中,Fn、FnOnce 和 FnMut 是定义闭包行为的三个核心 trait,它们决定了闭包如何捕获环境变量以及如何被调用。以下是清晰的解释和对比:
1. 核心概念
| Trait |
所有权要求 |
调用方式 |
典型用途 |
FnOnce |
获取所有权(self) |
只能调用 一次 |
消耗资源的闭包(如移动所有权) |
FnMut |
可变借用(&mut self) |
可调用 多次 (可修改状态) |
需要修改捕获变量的闭包 |
Fn |
不可变借用(&self) |
可调用 多次 (不可修改状态) |
只读取捕获变量的闭包 |
2. 详细解释
(1) FnOnce
- 所有权 :通过值(
self)捕获变量, 消耗 闭包自身。
- 调用限制 : 只能调用一次 (调用后闭包失效)。
- 使用场景 :需要转移所有权的操作(如
std::thread::spawn)。
- 示例 :
1 2 3 4 5 6 7
| let s = String::from("hello"); let consume = || { println!("{}", s); std::mem::drop(s); }; consume();
|
(2) FnMut
- 所有权 :通过可变引用(
&mut self)捕获变量, 可修改 环境。
- 调用限制 : 可多次调用 ,每次调用可能改变状态。
- 使用场景 :迭代器适配器(如
filter、map)中需要修改状态的闭包。
- 示例 :
1 2 3 4 5 6 7
| let mut count = 0; let mut increment = || { count += 1; println!("Count: {}", count); }; increment(); increment();
|
(3) Fn
- 所有权 :通过不可变引用(
&self)捕获变量, 只读 访问环境。
- 调用限制 : 可多次调用 ,且不改变闭包状态。
- 使用场景 :无需修改环境的操作(如事件处理器)。
- 示例 :
1 2 3 4 5
| let s = String::from("hello"); let print = || println!("{}", s); print(); print(); println!("{}", s);
|
3. 继承关系
1 2 3
| graph LR FnOnce --> FnMut FnMut --> Fn
|
- 规则 :
- 所有闭包都至少实现
FnOnce(因为每个闭包至少会被调用一次)。
- 实现
Fn 的闭包自动实现 FnMut 和 FnOnce。
- 实现
FnMut 的闭包自动实现 FnOnce。
5. 实际代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| fn run_once<F: FnOnce()>(f: F) { f(); }
fn run_mut<F: FnMut()>(mut f: F) { f(); f(); }
fn run<F: Fn()>(f: F) { f(); f(); }
fn main() { let s = String::from("hello");
run_once(|| { println!("{}", s); });
run_mut(|| { println!("{}", s); });
run(|| println!("{}", s)); }
|
6. 关键总结
-
FnOnce:用于一次性闭包,消耗所有权。
-
FnMut:用于需修改状态的闭包,可多次调用。
-
Fn:用于只读闭包,可安全多次调用。
- 编译器自动推断 :闭包实现哪些 trait 取决于 如何使用捕获变量 (而非如何声明)。
注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布