Rust 标准库的迭代器适配器数量多、职责细,不少开发者在链式调用时只能靠猜。本文是「Rust 迭代器方法全家桶」系列第三篇,按方法名字典序讲解第 21 到第 30 个方法,涵盖 flat_map、flatten、fuse、inspect、by_ref、collect、partition、try_fold、try_for_each 与 fold,并给出每个方法的行为边界。
flat_map:map 加 flatten 的两步合成
flat_map 的签名为 fn flat_map(self, f: F) -> FlatMap<Self, U, F>,约束是 Self: Sized、U: IntoIterator、F: FnMut(Self::Item) -> U。它相当于先执行 map、再执行 flatten,理解关键在于 flatten 的作用。
从输出类型看,collect 之前 flat_map 与单纯 map 的结果都表现为 impl Iterator<Item = char>,即逐字符的迭代器,因此再 collect 就能得到最终 String;而先 map 的中间结果类型是 impl Iterator<Item = impl Iterator<Item = char>>。标准库对 Chars 的定义本身就是一个迭代器类型,所以后者形成了嵌套结构,flatten 把外层容器剥掉,内层数据合并为 impl Iterator<Item = char>,交由末级 collect 回收。
flatten:一次只剥一层,可反复调用来去多层
flatten 的签名是 fn flatten(self) -> Flatten<Self>,约束为 Self::Item: IntoIterator。其用途是压平嵌套,将二维降为一维、三维降为二维,适用于一切实现 IntoIterator 的类型,Option 与 Result 同样在内。

文中对比了调用 into_iter 与 flatten 后返回类型签名的变化,说明后者在去掉外层嵌套上更为省事;Result 类型也可以借它解嵌套。需要注意,这种平坦化每次只去一层,示例中把三维结构先压成二维、再压成一维,依靠的正是 flatten 可连续多次调用。
fuse:遇到第一个 None 就熔断
fuse 的签名是 fn fuse(self) -> Fuse<Self>,生成新迭代器,典型适配器行为:迭代到第一个 None 即停止推进。示例自定义了迭代器 Alternate,其在 Some 与 None 之间交替取值,逻辑实现在 Iterator 特征的 next 方法中——偶数下标有值,奇数下标产生 None,元素序列为 Some(0)、None、Some(2)、None、Some(4)、None、Some(6)、None、Some(8) 等。
用 next() 手工推进,停在 Some(2) 加 None 的位置;随后 let mut iter = iter.fuse(); 从 Some(4) 继续,取得 Some(4) 后遇到下一个 None 即触发熔断,此后不再推进,剩余结果恒为 None。
inspect:链式逻辑的调试探针
inspect 的签名是 fn inspect(self, f: F) -> Inspect<Self, F>,约束为 F: FnMut(&Self::Item)。它作为适配器插在链式调用中间层级,典型用途是调试,插入一级不改变原有设计逻辑,如同外接了一个探头。

示例从 [1, 4, 2, 3] 里筛选偶数并求和。原始写法一开始不易看懂,于是插入两处 inspect,依打印输出即可判断程序意图:to filter: 1、to filter: 4、pass filter: 4、to filter: 2、pass filter: 2、to filter: 3。另一个示例则在 filter 剔除错误值之前插入 inspect,打印出 Parsing error: invalid digit found in string,直接显示成因。
by_ref:让原迭代器在被遍历后仍可继续引用
by_ref 的签名是 fn by_ref(&mut self) -> &mut Self,对当前迭代器实例建立可变借用。提供它的原因是,有些场景不希望原始迭代器因迭代而消耗掉:即便内部元素发生了变化,也期望之后还能再引用它,此时以引用方式迭代就是合适方案。示例最后仍可访问最初的 words 迭代器,完整呈现了这一过程。
collect:末级消费的最高频方法
collect 的签名是 fn collect(self) -> B,约束是 B: FromIterator<Self::Item>,常出现在链式调用末端。示例给出若干典型用法:用包括「金枪鱼符号」:: 在内的 turbofish 语法显式指定集合类型;集合元素类型可由上下文推导因此能以 _ 占位。
一个高频用法是把字符数组浓缩成字符串:hello 分别为 dgkkn 的下一个字母,这里经过两次 map 转换。由于 char 本身不能直接执行加 1,代码还能简化。标准库文档另有示例,说明如何从 Result 序列中取出第一个 Err 并收集全部 Ok:执行 collect 对应 FromIterator 特征的 from_iter,迭代容器中的 Result 元素,遇到第一个 Err 立即终止并返回它;若全部为 Ok,则把所有 Ok 内的值收进集合(此例为 Vec)并返回 Ok(Vec)。这是标准库对 Result 的专门实现,适用于只关心第一个错误的场景,但若想汇总全部错误,仍可借助 filter、map 等方法。

partition:按闭包条件拆成两个集合
partition 的签名是 fn partition(self, f: F) -> (B, B),约束为 B: Default + Extend<Self::Item>、F: FnMut(&Self::Item) -> bool,属于消费器,处于链式末级。闭包给出分区判定,满足者进入前一个集合,否则进入后一个。示例把 [1, 2, 3] 按偶数条件分区,第一分区为 [2],第二分区为 [1, 3]。
try_fold:可短路的折叠
try_fold 的签名是 fn try_fold(&mut self, init: B, f: F) -> R,约束 F: FnMut(B, Self::Item) -> R<B, Self>,可视为 fold 的安全版本,返回结果同样是 Try 类型。示例一计算 [1, 2, 3] 的和为 6,累加器初值写作 0i8 即 8 位有符号整型,过程不溢出,因此可用 Option 表示有可选输出的和。
示例二把序列元素调大:i8 上限为 127,10+20+30+100 加到 100 时溢出,且 100 已被消耗,因为是先加后才发现,迭代器仅剩 [40, 50],这种中途异常立刻终止的情形有个形象的名字——短路 short-circuit。
若想控制更精确的刹车,即让闭包计算过程中自己决定继续还是退出,可以用 ControlFlow 类型。示例在 1 到 30(不含 30)之间累加并要求不超过 127:按 n*(1+n)/2<=127 计算,n 最大取 15,和等于 15*16/2=120,再加即溢出;if…let…else 判断及时识别,并在 ControlFlow::Break() 的同时带出累加结果,相对只返回无效 None 更能展现细节。

另有一个把累加器拓宽到 u64 的示例,此时不会溢出,可完整算出 (1..=29) 的和,(1+29)*29/2=435,闭包要迭代完整个序列才退出,最终返回 435。
try_for_each:以 Err 提前收手的 for_each
try_for_each 的签名是 fn try_for_each(&mut self, f: F) -> R,约束 F: FnMut(Self::Item) -> R<B, Self>。名字带 try 的方法大多自带异常回退,要理解它先看 for_each 的不严密之处。示例引入文件系统,虚构若干实际硬盘上并不存在的文件,Path::new("no_tea.txt").with_extension("old") 期望的 no_tea.old 也不存在,于是第二步 rename 出现异常,try_for_each 返回 Err;而第一步写 stdout() 是成功的,返回 Ok。
带 ControlFlow 的示例中,从 2 到 99 里找第一个能整除 323 的数,很快找到 17,因为 323/17=19。ControlFlow 让闭包内部对继续或退出拥有更细的控制力。
fold:状态机式的折叠累加
fold 的签名是 fn fold(self, init: B, f: F) -> B,约束 F: FnMut(B, Self::Item) -> B。累加器相当于一个状态机,类型为泛型 B,可理解成 Builder,初始值为 init,最终返回该状态机的终值,行为由闭包 f 控制、f 接收状态机的输入初值,查看源码对 fold 的定义可以进一步确认。

示例用折叠状态机求数组元素之和,是链式编程中的自然写法。不过上面写法有纰漏:累加可能溢出。把 try_fold 的例子放到这里再演算,累加过程越过 i8 上限 127,运行即报 thread 'main' (543091) panicked at src/main.rs:5:43: attempt to add with overflow,并提示用 RUST_BACKTRACE=1 环境变量查看回溯——这正是 try_fold 存在的意义。
累加不限于数字,行为取决于闭包如何设计。另一个示例以宏格式化方式把一个数字序列写成括号嵌套形式;不用 fold 也有其它算法,但累加器写法更直观易懂。以 fold 累加器求和本质与常规 for 循环一致,不过少了为结果额外初始化的步骤,整个流程落在链中一次得出终值,简洁;官方称其效率不比 for 循环差,值得推荐多用。
小结:第 3 桶的 10 个方法里,flatten 的「一次只去一层」和 fold 的溢出 panic 是最容易踩坑的两处,try_fold 与 ControlFlow 则为累加提供了短路与自定义退出的能力;collect 兼顾类型收集与 Result 的错误提前返回,是末级消费的核心。
本篇为第 3 桶,后面还有第 4 桶。
