闭包与迭代器:高层表达,底层性能¶
这一章讲什么: 两个从函数式编程借来的机制。闭包(closure):可以捕获周围变量的 匿名函数;迭代器(iterator):处理一串元素的惰性流水线。读完你要会写闭包、 看懂 FnOnce/FnMut/Fn 三档约束、会用 map/filter/collect 组合迭代器—— 以及记住本章的实测结论:这两样东西用起来"很高级",跑起来和你手写底层循环一样快。
1. 顶层全景:两个机制,一条主线¶
主线例子是第 12 章的 minigrep——本章回头给它做两次" iterator 化"手术,顺带回答第 12 章欠下的债(那次 clone)。
闭包线: giveaway 例子 → 捕获三方式 → move → Fn 三档 trait → unwrap_or_else 的签名精读
迭代器线: Iterator trait 只有一个 next → 消费适配器(sum)/迭代适配器(map、filter)
→ Config::build 去 clone 化 → search 一行化 → 基准测试证明零成本
图说:闭包提供"可定制的行为",迭代器提供"可复用的循环",两者在第 10 章的 trait 体系上合流。
2. 闭包:能捕获环境的函数¶
闭包是可以存在变量里、以后调用的匿名函数;它和函数的关键区别:能捕获定义处环境的值。
主线例子(T 恤促销):user_preference.unwrap_or_else(|| self.most_stocked())——
用户有偏好就用自己的,没有就用库存最多的颜色。unwrap_or_else 接收一个无参、返回 T 的闭包;
Some 时闭包根本不被调用(所以叫 or_else:惰性),None 时才求值。亮点在于:闭包捕获了
self 的不可变引用,而标准库对 Inventory 一无所知——"带上环境一起传递"是函数做不到的1。
类型标注可选:闭包通常是局部的、短命的,编译器能推断参数和返回类型(第一次调用后类型就锁死,再传别的类型会 E0308)。要显式也行:|num: u32| -> u32 { ... }。和函数的对比表很直观:fn add_one_v1 (x: u32) -> u32 { x + 1 } 对 let add_one_v4 = |x| x + 1;2。
捕获的三种方式,一一对应函数参数的三种接法:只读 → 不可变引用;要改 → 可变引用(let mut borrows_mutably = || list.push(7);,定义后、调用前不许别人再借 list——第 4 章规则原样生效);move 关键字强制 夺走所有权——典型用途是传给新线程:闭包可能比定义它的环境活得久,引用会失效,所以必须 move3。
3. Fn 三档:闭包的"体检报告"¶
闭包对环境做了什么,决定它自动实现哪几个 trait——接收方用 trait bound 声明"我要哪种闭包":
| trait | 闭包干了什么 | 能调几次 |
|---|---|---|
FnOnce |
把捕获的值移出闭包体 | 一次 |
FnMut |
不移出,但可能改捕获的值 | 多次 |
Fn |
不改不动(或啥也没捕获) | 多次,可并发 |
三者是叠加关系:所有闭包至少实现 FnOnce4。
精读两个标准库签名,体会约束的精确性:
unwrap_or_else 要 F: FnOnce() -> T——body 里 Some 分支根本不调 f,None 才调一次,所以最宽的 FnOnce 就够;因为所有闭包都实现 FnOnce,它对调用方最宽容5。
sort_by_key 要 FnMut——它对切片每个元素调一次闭包,会多次调用。书里特意演示了"试图在闭包里 move 出环境值"的 E0507:一个把 String 推出闭包的闭包只实现了 FnOnce,过不了 FnMut 的 bound;正解是用可变计数器(捕获 &mut)而不是 move6。
一个不 capture 任何值时的小贴士:可以直接传函数名(unwrap_or_else(Vec::new)),函数也自动实现适用的 Fn trait5。
4. 迭代器:只实现一个方法的无限可能¶
迭代器负责"挨个取元素、知道何时取完"的全部逻辑;你不用再写索引循环。
v1.iter() 创建迭代器——但迭代器是惰性的,光创建什么都不发生7。
Iterator trait 的核心短得惊人:
pub trait Iterator {
type Item; // 关联类型(第 20 章),这里=迭代出的元素类型
fn next(&mut self) -> Option<Self::Item>; // 唯一必须实现的方法
// 其余几十个方法都有默认实现
}
next 每次吐一个 Some(元素),吐完返回 None。v1.iter() 吐出的是不可变引用(Some(&1));要 owned 值用 into_iter,要可变引用用 iter_mut。调 next 会改变迭代器内部状态,所以迭代器本身要 mut8。
方法分两类,这是用迭代器的总开关:
- 消费适配器(consuming adapter):内部调 next、把迭代器用光。
sum()求和;用完迭代器作废。 - 迭代适配器(iterator adapter):不消费,返回改造过的新迭代器。
map(|x| x + 1)、filter(...)。惰性由此而来:v1.iter().map(...);单独一行什么也不做,还会被警告;要见效必须接消费适配器——最常用collect(收成集合)9。
filter 的闭包吃一个元素、返回 bool:true 的留下。主线例子 shoes_in_size:into_iter + filter(闭包捕获外部参数 shoe_size)+ collect,三行替掉一整个手动循环10。
5. 回头改造 minigrep¶
改造一:Config::build 去 clone。 第 12 章为 owned 字段 clone 了两次字符串;现在让 build 直接接收 env::args() 返回的迭代器的所有权——签名 mut args: impl Iterator<Item = String>。体内不用长度检查和下标,全改 next():第一个 next 丢掉程序名;第二个是 query,None 就 Err("Didn't get a query string");第三个是 file_path。从迭代器里拿出来的是 owned 的 String,move 进 Config,clone 连根拔掉——"每次 next 的调用点都有专属错误信息"还顺便取代了 args.len() < 3 的笼统检查11。
改造二:search 一行化。
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines().filter(|line| line.contains(query)).collect()
}
可变中间变量 results 消失。书给了两个进阶理由:函数式风格倾向减少可变状态;没有共享的可变向量,将来想做并行搜索也少一道坎。再进一步:把 collect 去掉、返回 impl Iterator<Item = &'a str>,run 里的 for 循环就能边找边打,大文件不再等全部收集完12。
6. 性能:零成本抽象的实测证据¶
for 循环版 vs 迭代器版的 search,基准是拿整本《福尔摩斯探案集》搜 "the":
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
(两个数字在噪声范围内打平;书明说重点不是证明等价,而是给个量级感)
书的结论:迭代器虽是高层抽象,编译产物和手写底层代码大致相同——循环展开、消除数组越界检查等优化照常生效。这就是 Rust 的"零成本抽象":不用不付钱,用了也写不出更好的手写版(引 Stroustrup 对 C++ 零开销原则的定义)13。
7. 作者的判断与证据¶
- "迭代器和闭包不影响运行时性能"——有基准数据支撑(上表)13。
- "多数 Rust 程序员偏好迭代器风格"——社区习惯陈述,理由是高层次意图更突出14。
- "闭包类型标注可以省"——设计立场,理由是闭包不暴露在公开接口上2。
判断(我们的,不是书里的): 本章是第 10 章三件套的第一个大规模实战: unwrap_or_else 的
FnOncebound 和 sort_by_key 的FnMutbound 告诉我们—— trait bound 不是装饰,是调用契约:它把"我会怎么调用你"写进了类型。 读懂标准库方法的 bound,比记住任何闭包规则都更接近本质。 如果错,会错在: "零成本"是趋势性结论而非保证——迭代器链条在复杂组合下 也可能阻碍优化(本书未涉及);基准数字只对那个特定测试成立,书自己也提醒要换文本、 换查询词再验。
8. 边界与局限¶
- 关联类型(type Item)只露了个脸,第 20 章才讲。
- 基准测试代码不展示(且 benchmark 需 nightly,第 11 章提过)。
- 闭包与异步/并发的结合(move 闭包进线程)只给了预告,第 16 章展开。
9. 可带走的¶
- 闭包 = 可捕获环境的匿名函数;
|内是参数,标注可省。 - 捕获三方式:不可变引用 / 可变引用 / move 夺所有权(传线程必须 move)。
- FnOnce(移出,一次)/ FnMut(可改,多次)/ Fn(只读,多次)——接收方按需求下 bound。
- 不捕获任何东西时,可以直接传函数名代替闭包。
- 迭代器惰性:创建不算数,消费适配器(sum/collect/for)才驱动。
- 只实现 next 即可接入 Iterator 的全部默认方法;iter/into_iter/iter_mut 分别产引用/值/可变引用。
- map/filter 组管道,collect 收口;链条里缺消费端 = 什么都没发生。
- impl Iterator<Item = String> 当参数:拿走迭代器所有权、逐 next、每个调用点专属报错。
- 迭代器风格和循环风格性能打平(本书基准)——优先选表达更清楚的那种。
10. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| giveaway 与闭包初识 | Chapter 13: Functional Language Features | text/20-ch13-chapter-13-functional-language-features-iterator.txt:67(搜「unwrap_or_else」) |
| 类型推断与标注 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:147(搜「Inferring and Annotating」) |
| 捕获三方式与 move | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:231(搜「Capturing References」) · text/20-ch13-chapter-13-functional-language-features-iterator.txt:295(搜「move keyword」) |
| Fn 三档定义 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:347(搜「FnOnce applies」) |
| unwrap_or_else 签名 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:362(搜「pub fn unwrap_or_else」) |
| sort_by_key 与 FnMut | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:438(搜「sort_by_key」) · text/20-ch13-chapter-13-functional-language-features-iterator.txt:478(搜「cannot move out」) |
| Iterator trait 与 next | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:565(搜「Iterator Trait」) · text/20-ch13-chapter-13-functional-language-features-iterator.txt:596(搜「Some(&1)」) |
| 消费与迭代适配器 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:623(搜「consuming adapters」) · text/20-ch13-chapter-13-functional-language-features-iterator.txt:663(搜「unused Map」) |
| filter 捕获环境 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:712(搜「Shoe」) |
| Config::build 去 clone | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:865(搜「impl Iterator<Item = String>」) |
| search 迭代器化 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:952(搜「filter( |
| 性能基准 | 同上 | text/20-ch13-chapter-13-functional-language-features-iterator.txt:998(搜「bench_search」) |
-
出处:「Chapter 13: Functional Language Features」第 128 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:128,搜「most_stocked」)与第 145 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:145,搜「not able to capture」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 147 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:147,搜「Inferring and Annotating」)。 ↩↩ -
出处:「Chapter 13: Functional Language Features」第 232 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:232,搜「three ways」)与第 295 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:295,搜「move keyword」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 347 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:347,搜「FnOnce applies」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 362 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:362,搜「pub fn unwrap_or_else」)。 ↩↩ -
出处:「Chapter 13: Functional Language Features」第 438 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:438,搜「sort_by_key」)与第 478 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:478,搜「cannot move out」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 524 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:524,搜「lazy」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 565 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:565,搜「Iterator Trait」)与第 596 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:596,搜「Some(&1)」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 623 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:623,搜「consuming adapters」)与第 663 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:663,搜「unusedMap」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 712 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:712,搜「Shoe」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 865 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:865,搜「impl Iterator<Item = String>」)与第 892 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:892,搜「args.next」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 952 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:952,搜「filter(|line|」)。 ↩ -
出处:「Chapter 13: Functional Language Features」第 998 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:998,搜「bench_search」)与第 1008 段(text/20-ch13-chapter-13-functional-language-features-iterator.txt:1008,搜「compiled down」)。 ↩↩ -
出处:「Chapter 13: Functional Language Features」第 973 段(
text/20-ch13-chapter-13-functional-language-features-iterator.txt:973,搜「Choosing Between Loops」)。 ↩