Skip to content

类型与捕获

> 掌握闭包的类型推断机制,理解三种捕获方式:不可变借用、可变借用和 move。

类型推断与标注

类型推断机制

rust
fn 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
    // 一旦类型确定,就不能改变
}
▶ Run

为什么闭包类型不可命名

rust
// 每个闭包都有唯一的、编译器生成的类型
// 这个类型是私有的、不可命名的

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));
}
▶ Run

显式类型标注

rust
fn 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 };

    // 推荐:让编译器推断,只在必要时标注
}
▶ Run

捕获环境变量

闭包如何捕获环境

┌─────────────────────────────────────────────────────┐
│           闭包捕获环境的三种方式                    │
├─────────────────────────────────────────────────────┤
│                                                     │
│  1. 不可变借用 (&T)                                 │
│     • 闭包只读访问外部变量                          │
│     • 外部变量在闭包外仍然可用                      │
│     • 对应 Fn trait                                │
│                                                     │
│  2. 可变借用 (&mut T)                               │
│     • 闭包可以修改外部变量                          │
│     • 闭包调用期间外部变量不可用                   │
│     • 对应 FnMut trait                             │
│                                                     │
│  3. 获取所有权 (T)                                  │
│     • 使用 move 关键字                             │
│     • 外部变量移入闭包                              │
│     • 闭包外不再可用                                │
│     • 对应 FnOnce trait                            │
│                                                     │
└─────────────────────────────────────────────────────┘

不可变借用示例

rust
fn main() {
    let x = 42;  // 不可变变量

    // 闭包不可变借用 x
    let print_x = || {
        println!("x = {}", x);
    };

    print_x();  // x = 42
    print_x();  // 可以多次调用

    // x 仍然可用
    println!("x = {}", x);  // x = 42
}
▶ Run

可变借用示例

rust
fn 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);
}
▶ Run

move 关键字

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

    // 使用 move 获取 s 的所有权
    let print_s = move || {
        println!("{}", s);
    };

    print_s();

    // s 已移入闭包,不能再使用
    // println!("{}", s);  // ❌ 错误
}
▶ Run

何时使用 move

rust
// 场景 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)
▶ Run