第 13 章:迭代器与闭包
本章目标:使用闭包捕获环境变量,用迭代器组合数据转换,并改进第 12 章的搜索项目。
闭包
闭包是可赋值、传递或立即调用的匿名函数。编译器通常能推断参数和返回类型;闭包也可以捕获定义环境中的值:
let threshold = 10;
let is_large = |value: &i32| *value > threshold;
assert!(is_large(&12));闭包按需借用或取得捕获值。使用 move 可将捕获值移动进闭包,常用于把闭包交给线程或异步任务。捕获方式决定闭包实现的 Fn、FnMut 或 FnOnce 特性。
闭包会按其调用方式实现一个或多个调用 Trait:Fn 可重复调用且只共享借用捕获值;FnMut 可重复调用并修改捕获状态;FnOnce 至少可调用一次,可能消耗捕获值。编译器根据闭包主体实际做的事推断所需 Trait。
let color = String::from("green");
let print_color = || println!("{color}"); // 只读借用,可重复调用
print_color();
print_color();
let mut count = 0;
let mut increment = || count += 1; // 可变借用
increment();闭包定义时编译器会为捕获环境生成匿名类型。标准库的 unwrap_or_else、排序和线程 API 都会接收闭包,因此理解捕获与调用 Trait 能帮助读懂这些接口。
迭代器与惰性适配器
迭代器逐项产生值。map、filter 等适配器通常是惰性的,直到消费操作(如 collect、sum 或 for)发生才执行:
let values = [1, 2, 3, 4, 5];
let squares: Vec<i32> = values
.iter()
.filter(|value| **value % 2 == 1)
.map(|value| *value * *value)
.collect();
assert_eq!(squares, vec![1, 9, 25]);iter() 借用元素,iter_mut() 可变借用,into_iter() 通常消费集合并移出元素。选择哪种迭代器会影响所有权和后续能否继续使用集合。
Iterator Trait 的核心方法是 next,返回 Option<Self::Item>。迭代器适配器(如 map、filter、take、zip)返回新的迭代器;消费者(如 collect、sum、fold)遍历并产生最终结果。迭代器通常惰性求值,所以未被消费的适配器不会执行。
let sum: i32 = [1, 2, 3].iter().sum();
let doubled: Vec<_> = [1, 2, 3].iter().map(|n| n * 2).collect();
assert_eq!(sum, 6);
assert_eq!(doubled, vec![2, 4, 6]);实现 Iterator 时要定义关联类型 Item 和 next。自定义迭代器可以复用标准适配器,而不必自己实现整条数据处理链。
改进搜索项目
第 12 章的 search 可以用迭代器表达:
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}迭代器版本避免手动创建和逐项填充临时向量。闭包表达过滤条件,使数据流更集中。对 minigrep 来说,优化后应保留已有测试,确保行为没有改变。
还可以把筛选函数改为接收谓词闭包,让调用者决定匹配规则:
fn search_with<'a>(
contents: &'a str,
predicate: impl Fn(&str) -> bool,
) -> Vec<&'a str> {
contents.lines().filter(|line| predicate(line)).collect()
}生产代码可将大小写策略放在配置层,再选择相应谓词。测试应保证重构后结果与原始搜索保持一致。
性能思路
迭代器适配器常通过单态化和优化编译为高效循环。手写 for 循环和迭代器链最终可能生成相近机器码;清晰表达意图的迭代器不必然更慢。性能不能只凭语法判断,应测量实际工作负载,并避免为微小数据量牺牲可读性。
小结与练习
- 闭包捕获环境,捕获方式会影响所有权和调用能力。
- 迭代器适配器描述处理步骤,消费操作触发计算。
- 练习:将第 12 章搜索结果改为返回迭代器,并更新调用端和测试。
再练习:实现产生斐波那契数的自定义迭代器;用 take 限制项数,并用 map 和 sum 计算结果;为大小写不敏感搜索提取闭包谓词。
下一章:更多 Cargo 与 crates.io。
官方对应章节:Functional Language Features: Iterators and Closures。