闭包

在 Rust 中,FnFnOnceFnMut 是定义闭包行为的三个核心 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(); // 第一次调用(成功)
// consume(); // 错误!闭包已失效

​(2) FnMut

  • ​ 所有权 ​​:通过可变引用(&mut self)捕获变量,​​ 可修改 ​​ 环境。
  • ​ 调用限制 ​​:​​ 可多次调用 ​​,每次调用可能改变状态。
  • ​ 使用场景 ​​:迭代器适配器(如 filtermap)中需要修改状态的闭包。
  • ​ 示例 ​​:
1
2
3
4
5
6
7
let mut count = 0;
let mut increment = || {
count += 1; // 修改捕获的变量
println!("Count: {}", count);
};
increment(); // Count: 1
increment(); // Count: 2(状态被修改)

​(3) Fn

  • ​ 所有权 ​​:通过不可变引用(&self)捕获变量,​​ 只读 ​​ 访问环境。
  • ​ 调用限制 ​​:​​ 可多次调用 ​​,且不改变闭包状态。
  • ​ 使用场景 ​​:无需修改环境的操作(如事件处理器)。
  • ​ 示例 ​​:
1
2
3
4
5
let s = String::from("hello");
let print = || println!("{}", s); // 只读借用
print(); // "hello"
print(); // "hello"(状态不变)
println!("{}", s); // 外部仍可访问(未移动)

​3. 继承关系 ​

1
2
3
graph LR
FnOnce --> FnMut
FnMut --> Fn
  • ​ 规则 ​​:
    • 所有闭包都至少实现 FnOnce(因为每个闭包至少会被调用一次)。
    • 实现 Fn 的闭包自动实现 FnMutFnOnce
    • 实现 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
// 接受 FnOnce 的函数
fn run_once<F: FnOnce()>(f: F) {
f();
}

// 接受 FnMut 的函数
fn run_mut<F: FnMut()>(mut f: F) {
f();
f(); // 可多次调用
}

// 接受 Fn 的函数
fn run<F: Fn()>(f: F) {
f();
f(); // 可多次调用
}

fn main() {
let s = String::from("hello");

// FnOnce 示例
run_once(|| {
println!("{}", s); // 移动所有权
// drop(s); // 若显式消耗,闭包只能调用一次
});

// FnMut 示例
run_mut(|| {
// 无法修改 s(未声明 mut),但可调用多次
println!("{}", s);
});

// Fn 示例
run(|| println!("{}", s)); // 只读借用
}

​6. 关键总结 ​

  • FnOnce​:用于一次性闭包,消耗所有权。
  • FnMut​:用于需修改状态的闭包,可多次调用。
  • Fn​:用于只读闭包,可安全多次调用。
  • ​ 编译器自动推断 ​​:闭包实现哪些 trait 取决于 ​​ 如何使用捕获变量 ​​(而非如何声明)。

注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布