学习笔记十三—— 理解 Rust 闭包:从语法到 impl Fn vs Box<dyn Fn>

🧠 理解 Rust 闭包:从语法到 impl Fn vs Box


📚 目录

  1. 闭包是什么?和普通函数有什么不同?
  2. 闭包的语法长什么样?
  3. 闭包"捕获变量"是什么意思?
  4. 闭包和所有权的关系
  5. Fn、FnMut、FnOnce 三种闭包类型的区别和例子
  6. 什么是 Trait?闭包为什么要用 Trait?
  7. 为什么闭包"没有具体类型"?
  8. impl Fn 和 Box 的区别:底层逻辑 + 使用示例
  9. 常见闭包错误示例解析
  10. 总结与记忆建议

1️⃣ 闭包是什么?和函数有什么不同?

Rust 中的闭包是"一种匿名函数",它和普通函数的不同在于:

它可以使用外部作用域的变量

📌 举个例子:

rust 复制代码
fn main() {
    let name = "Tom";
    let greet = || println!("Hello, {}", name); // 使用了外部变量 name
    greet(); // 输出:Hello, Tom
}

这个 || println!(...) 就是闭包,虽然没有参数,但它能自动"记住"外部的 name


2️⃣ 闭包的语法长什么样?

rust 复制代码
let closure = |参数| 表达式;

🧪 示例:

rust 复制代码
let square = |x: i32| x * x;
println!("{}", square(4)); // 输出 16

参数可以省略类型,Rust 会自动推断:

rust 复制代码
let double = |x| x * 2;

3️⃣ 闭包"捕获变量"是什么意思?

闭包之所以强大,是因为它可以用外部的变量,比如:

rust 复制代码
let count = 5;
let show = || println!("{}", count);

这个闭包自动"借用了"变量 count。它不像普通函数必须把变量作为参数。

这种"带着环境走"的能力叫捕获变量


4️⃣ 闭包和所有权的关系

Rust 中使用变量要遵守所有权规则,闭包捕获变量时有三种方式:

捕获方式 说明 所属 Trait
借用(&T) 只读 Fn
可变借用(&mut T) 修改变量 FnMut
移动(T) 拿走变量所有权 FnOnce

Rust 会根据闭包内部的行为自动判断。


5️⃣ Fn、FnMut、FnOnce 的区别

Fn:只读、可多次调用

rust 复制代码
let name = "Alice";
let say_hi = || println!("Hi, {}", name); // 只读借用
say_hi();
say_hi(); // OK,多次调用

FnMut:可修改外部变量

rust 复制代码
let mut count = 0;
let mut inc = || count += 1; // 可变借用
inc();
inc();
println!("{}", count); // 输出 2

FnOnce:拿走变量所有权,只能用一次

rust 复制代码
let s = String::from("hi");
let consume = move || println!("{}", s); // s 被 move 进闭包
consume();
// consume(); ❌ 错:值已被使用

6️⃣ 什么是 Trait?闭包为什么要用 Trait?

Trait(特质)就是 Rust 中的"能力接口"。

谁实现了某个 Trait,就可以被当成"具有某种能力"的对象使用。

闭包没有固定类型,只能通过它实现的 Trait 来使用,比如:

  • Fn() 表示能多次调用
  • FnMut() 表示可变调用
  • FnOnce() 表示调用一次

7️⃣ 为什么闭包"没有具体类型"?

❓ 你写过这样的代码吗?

rust 复制代码
let c = |x| x + 1;
// let f: ??? = c; // 编译器报错!闭包没有类型名

这是因为:

Rust 中的闭包是编译器自动生成的匿名结构体,你无法直接用名字去写它的类型。

🔍 其实编译器背后大致生成了一个这样的结构体:

rust 复制代码
struct Closure {
    x: i32
}
impl Fn(i32) for Closure {
    fn call(&self, y: i32) -> i32 {
        y + self.x
    }
}

所以你只能通过它实现的 Fn 系列 Trait 来"访问"它。


8️⃣ impl Fn 和 Box 的区别:底层逻辑 + 使用示例

🧩 这两种写法都能接收闭包:

✅ 写法一:impl Fn()(静态分发)
rust 复制代码
fn call_twice(f: impl Fn()) {
    f();
    f();
}

fn main() {
    let name = "Tom";
    let say_hi = || println!("Hi {}", name);
    call_twice(say_hi); // OK
}
  • 编译时就知道闭包的类型(编译器展开 inline)
  • ,无额外开销

✅ 写法二:Box<dyn Fn()>(动态分发)
rust 复制代码
fn call_twice(f: Box<dyn Fn()>) {
    f();
    f();
}

fn main() {
    let name = "Tom".to_string();
    let say_hi = move || println!("Hi {}", name);
    call_twice(Box::new(say_hi)); // OK
}
  • Box<dyn Fn()> 是一个Trait 对象
  • 用于运行时决定具体调用哪个函数(通过虚表 vtable 实现)
  • 更加灵活(适合多个不同类型的闭包集合)

🧪 类比理解:

对比维度 impl Fn() Box<dyn Fn()>
类型是否确定 编译时确定 编译时未知,运行时查表
性能 快(无虚表) 稍慢(要查 vtable)
是否堆分配 不需要
使用场景 简单、性能敏感场景 复杂或多种类型的集合场景

🧠 类比:点菜 vs 做饭

  • impl Fn() 就像自己做饭,提前准备、速度快;
  • Box<dyn Fn()> 就像去餐厅点菜,灵活但慢一点,因为要看菜单(vtable)。

❗ 什么时候必须用 Box<dyn Fn()>?

比如你要存多个不同闭包进 Vec:

rust 复制代码
let mut funcs: Vec<Box<dyn Fn()>> = vec![];

funcs.push(Box::new(|| println!("hello")));
funcs.push(Box::new(|| println!("world")));

for f in funcs {
    f(); // 运行时查表调用
}

不能用 Vec<impl Fn()>,因为每个闭包的底层结构体不同,大小不一样,Rust 不允许放在一个 Vec 里。


9️⃣ 常见闭包错误示例解析

❌ 错误:借用了可变变量,但用 Fn

rust 复制代码
fn twice<F: Fn()>(f: F) {
    f();
    f();
}

let mut count = 0;
let mut closure = || count += 1;

twice(closure); // ❌ 错误!因为 closure 是 FnMut

✅ 修复方式:

rust 复制代码
fn twice<F: FnMut()>(mut f: F) {
    f();
    f();
}

🔟 总结与记忆建议

概念 通俗解释
闭包 能记住外部变量的小函数
Trait 能力接口,如 Fn 表示可调用
impl Fn() 编译期固定,快
Box<dyn Fn()> 运行时多态,灵活
Trait 对象 抽象能力 + 虚表,运行时查找实际调用方法

🧩 记忆口诀

闭包没名字,Trait 来代管;

impl 是静态,Box 是多态;

多种闭包放 Vec,必须用 Box;

借改拿三种捕获,决定 Fn 哪种管。

相关推荐
MarkHD13 分钟前
智能体在车联网中的应用:第51天 模仿学习与离线强化学习:破解数据效率与安全困局的双刃剑
学习·安全
Drawing stars3 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
崇山峻岭之间3 小时前
Matlab学习记录33
开发语言·学习·matlab
玄〤4 小时前
黑马点评中 VoucherOrderServiceImpl 实现类中的一人一单实现解析(单机部署)
java·数据库·redis·笔记·后端·mybatis·springboot
科技林总4 小时前
【系统分析师】3.5 多处理机系统
学习
芯思路5 小时前
STM32开发学习笔记之三【按键】
笔记·stm32·学习
Lips6115 小时前
2026.1.11力扣刷题笔记
笔记·算法·leetcode
charlie1145141916 小时前
从 0 开始的机器学习——NumPy 线性代数部分
开发语言·人工智能·学习·线性代数·算法·机器学习·numpy
咚咚王者6 小时前
人工智能之核心基础 机器学习 第十二章 半监督学习
人工智能·学习·机器学习
袁气满满~_~6 小时前
Python数据分析学习
开发语言·笔记·python·学习