类型与捕获
> 掌握闭包的类型推断机制,理解三种捕获方式:不可变借用、可变借用和 move。
类型推断与标注
类型推断机制
rust
▶ Runfn main() {
// 编译器根据第一次使用推断类型
let add_one = |x| x + 1;
let result1 = add_one(5); // x 推断为 i32
let result2 = add_one(100); // ✅ 也是 i32
// let result3 = add_one(5.0); // ❌ 错误:不能是 f64
// 一旦类型确定,就不能改变
}为什么闭包类型不可命名
rust
▶ Run// 每个闭包都有唯一的、编译器生成的类型
// 这个类型是私有的、不可命名的
fn main() {
let closure = |x| x + 1;
// 以下写法都是错误的:
// let c: ??? = closure; // 无法写出类型!
// 解决方案 1:使用泛型
fn use_closure<F>(f: F)
where
F: Fn(i32) -> i32,
{
println!("{}", f(5));
}
use_closure(closure);
// 解决方案 2:使用 trait 对象
let boxed: Box<dyn Fn(i32) -> i32> = Box::new(closure);
println!("{}", boxed(5));
}显式类型标注
rust
▶ Runfn main() {
// 方式 1:标注变量类型
let add_one: fn(i32) -> i32 = |x: i32| x + 1;
// 方式 2:标注参数类型
let add_one = |x: i32| x + 1;
// 方式 3:标注返回类型
let add_one = |x: i32| -> i32 { x + 1 };
// 方式 4:完整标注
let add_one: fn(i32) -> i32 = |x: i32| -> i32 { x + 1 };
// 推荐:让编译器推断,只在必要时标注
}捕获环境变量
闭包如何捕获环境
┌─────────────────────────────────────────────────────┐
│ 闭包捕获环境的三种方式 │
├─────────────────────────────────────────────────────┤
│ │
│ 1. 不可变借用 (&T) │
│ • 闭包只读访问外部变量 │
│ • 外部变量在闭包外仍然可用 │
│ • 对应 Fn trait │
│ │
│ 2. 可变借用 (&mut T) │
│ • 闭包可以修改外部变量 │
│ • 闭包调用期间外部变量不可用 │
│ • 对应 FnMut trait │
│ │
│ 3. 获取所有权 (T) │
│ • 使用 move 关键字 │
│ • 外部变量移入闭包 │
│ • 闭包外不再可用 │
│ • 对应 FnOnce trait │
│ │
└─────────────────────────────────────────────────────┘不可变借用示例
rust
▶ Runfn main() {
let x = 42; // 不可变变量
// 闭包不可变借用 x
let print_x = || {
println!("x = {}", x);
};
print_x(); // x = 42
print_x(); // 可以多次调用
// x 仍然可用
println!("x = {}", x); // x = 42
}可变借用示例
rust
▶ Runfn main() {
let mut counter = 0;
// 闭包可变借用 counter
let mut increment = || {
counter += 1;
println!("counter = {}", counter);
};
increment(); // counter = 1
increment(); // counter = 2
increment(); // counter = 3
// 闭包调用期间,counter 不可用
// println!("{}", counter); // ❌ 错误
// 闭包使用后,counter 恢复可用
println!("最终 counter = {}", counter);
}move 关键字
rust
▶ Runfn main() {
let s = String::from("hello");
// 使用 move 获取 s 的所有权
let print_s = move || {
println!("{}", s);
};
print_s();
// s 已移入闭包,不能再使用
// println!("{}", s); // ❌ 错误
}何时使用 move
rust
▶ Run// 场景 1:闭包在新线程中使用
use std::thread;
fn main() {
let data = String::from("线程数据");
let handle = thread::spawn(move || {
println!("线程中:{}", data);
});
// data 已移入线程
handle.join().unwrap();
}
// 场景 2:闭包生命周期超过当前作用域
fn create_closure() -> impl Fn() {
let s = String::from("数据");
// 必须使用 move,否则 s 在函数结束时释放
move || println!("{}", s)
}
// 场景 3:需要转移所有权给其他结构
struct Holder {
action: Box<dyn Fn()>,
}
impl Holder {
fn new(s: String) -> Self {
Holder {
// 必须使用 move
action: Box::new(move || println!("{}", s)),
}
}
}
---
## 小结
- 闭包类型由编译器推断,一旦确定就不能改变
- 每个闭包有唯一的、编译器生成的类型,不可命名
- 三种捕获方式:不可变借用、可变借用、move
- `move` 关键字将环境变量所有权转移到闭包中
- move 闭包常用于线程和新线程场景
## 练习题
详见:[练习题](../../exercises/18-closures)