核心特性

第13课:闭包

🎯 本课目标

掌握Rust闭包语法、捕获方式、Fn/FnMut/FnOnce trait,以及闭包的常见应用场景。

1. 闭包基础

fn main() {
    // 基本闭包
    let add = |a, b| a + b;
    println!("3+4={}", add(3, 4));

    // 带类型标注
    let multiply: fn(i32, i32) -> i32 = |a, b| a * b;

    // 多行闭包
    let complex = |x| {
        let y = x * 2;
        let z = y + 1;
        z * z
    };

    // 捕获环境变量
    let factor = 3;
    let scale = |x| x * factor;  // 捕获factor
    println!("5*3={}", scale(5));
}

✅ 编译验证通过

2. 捕获方式

fn main() {
    // FnOnce — 获取所有权(move)
    let name = String::from("Alice");
    let greet = move || {      // move关键字强制获取所有权
        println!("Hello, {}!", name);
    };
    greet();
    // println!("{}", name);  // ❌ name已被移入闭包

    // FnMut — 可变借用
    let mut count = 0;
    let mut increment = || {
        count += 1;  // 可变借用count
        count
    };
    println!("count={}", increment());
    println!("count={}", increment());
    println!("外部count={}", count);  // ✅ FnMut后借用释放

    // Fn — 不可变借用
    let x = 10;
    let read_x = || x;  // 不可变借用
    println!("x={}", read_x());
    println!("x={}", x);  // ✅ 仍可用
}

✅ 编译验证通过

三种Fn trait

Trait捕获方式可调用次数
FnOnce获取所有权一次
FnMut可变借用多次(需mut)
Fn不可变借用多次

关系:FnFnMutFnOnce(实现了Fn的也实现了FnMut和FnOnce)

3. 闭包作为参数和返回值

// 闭包作为参数
fn apply<F>(f: F, x: i32) -> i32
where F: Fn(i32) -> i32,
{
    f(x)
}

// 闭包作为返回值
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

// 或用Box
fn make_multiplier(x: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |y| x * y)
}

fn main() {
    let add5 = make_adder(5);
    println!("5+3={}", add5(3));

    let mul3 = make_multiplier(3);
    println!("3*4={}", mul3(4));
}

✅ 编译验证通过

4. 闭包实战应用

fn main() {
    // 缓存/记忆化
    use std::collections::HashMap;
    let mut cache: HashMap<i32, i32> = HashMap::new();
    let mut expensive = |n| {
        *cache.entry(n).or_insert_with(|| {
            println!("计算中...");
            n * n
        })
    };
    println!("{}", expensive(5));  // 计算
    println!("{}", expensive(5));  // 缓存命中!

    // 排序自定义键
    let mut people = vec![("Alice", 30), ("Bob", 25), ("Charlie", 35)];
    people.sort_by_key(|p| p.1);  // 按年龄排序

    // 条件过滤
    let threshold = 28;
    let older: Vec<_> = people.iter()
        .filter(|p| p.1 > threshold)
        .collect();
}

✅ 编译验证通过

5. 闭包的内存模型

闭包在内存中 = 捕获的环境 + 函数指针 let factor = 3; let scale = |x| x * factor; 闭包scale的内存: ┌───────────────────┐ │ 函数指针 │ → |x| x * captured_factor │ 捕获的环境: │ │ &factor → 3 │ ← 不可变借用(Fn) └───────────────────┘ let mut count = 0; let mut inc = || { count += 1; count }; 闭包inc的内存: ┌───────────────────┐ │ 函数指针 │ → || { *captured += 1; *captured } │ 捕获的环境: │ │ &mut count → 0 │ ← 可变借用(FnMut) └───────────────────┘ let name = String::from("Alice"); let greet = move || println!("Hi, {}", name); 闭包greet的内存: ┌───────────────────┐ │ 函数指针 │ │ 捕获的环境: │ │ name: String │ ← 所有权转移(FnOnce) │ ptr → "Alice" │ └───────────────────┘

📝 练习题

练习1:柯里化

实现一个柯里化函数:add(a)(b) = a + b。

练习2:事件系统

用闭包实现一个简单的事件系统,支持注册和触发。

练习3:Lazy评估

实现一个Lazy<T>类型,只在第一次访问时计算值。

🏆

成就解锁:闭包大师

掌握闭包——Rust函数式编程的核心


📚 扩展阅读

以下资源帮助你深入学习本课主题:

🔑 关键术语回顾

本课涉及的核心概念,确保你理解每一个:

术语说明
所有权Rust内存管理的核心机制,每个值有唯一所有者
借用通过引用访问数据,不获取所有权
生命周期引用有效的范围,编译期分析工具
traitRust的接口/抽象机制,类似其他语言的接口
泛型参数化类型,零成本抽象
模式匹配强大的数据解构和分支机制
零成本抽象高层抽象不引入运行时开销
fearless concurrency编译器保证线程安全

💬 学习建议

  1. 动手编码 — 每个代码示例都在本地运行一遍
  2. 修改实验 — 故意改错代码,看编译器报什么错
  3. 完成练习 — 每课的练习题是巩固知识的关键
  4. 阅读源码 — 看标准库和优秀开源项目的实现
  5. 写项目 — 真正掌握需要构建真实项目

🤔 常见问题

Q: Rust学习曲线真的很陡吗?

A: 前期确实需要适应所有权和借用检查器,但一旦理解了,这些概念会让你的代码更可靠。大多数人2-4周就能上手。

Q: Rust适合什么项目?

A: 系统编程、Web服务、CLI工具、嵌入式、WASM、网络服务、数据库等。基本上需要性能和安全的地方都适合。

Q: 遇到编译错误怎么办?

A: Rust编译器的错误信息非常友好!仔细阅读,通常会指出问题所在和修复建议。也可以用cargo clippy获取更多提示。