讲解
闭包(closure)是可以捕获周围环境的匿名函数,写法是竖线包参数:|x| x + 1。类型标注大多可省(编译器从用法推断);捕获环境分三种强度:只读借用(默认,够用就好)、可变借用(闭包里改外部变量,要求闭包声明 mut)、拿走所有权(move 关键字,多线程场景必须,第 24 章见)。闭包实现了 Fn/FnMut/FnOnce 三个 trait 中的一种或多种,这决定了它能不能被多次调用、能不能改环境——细节先了解,用到时编译器会提示。
迭代器(iterator)是 Rust 处理序列的统一抽象,核心方法就一个:next() 返回 Option(None 表示结束)。但真正日常用的是两类组合方法:适配器(adapter)——map、filter、enumerate、take、rev,消费一个迭代器产出新迭代器,惰性执行、不耗性能;消费者(consumer)——collect、sum、count、for_each、fold,真正驱动迭代跑起来并产出结果。
「惰性」是关键提醒:v.iter().map(|x| x * 2) 本身什么都不做,没有消费者收尾这行代码等于没写(编译器会发警告)。性能上尽管放心:迭代器链经编译器优化后与手写循环等价(零成本抽象),所以放心写声明式的链式代码,不必为性能退回下标循环。
示例
闭包的三种捕获:
fn main() {
// 只读借用环境
let factor = 3;
let multiply = |x: i32| x * factor;
assert_eq!(multiply(4), 12);
println!("factor 仍可用:{factor}");
// 可变借用:闭包自己也要 mut
let mut total = 0;
let mut accumulate = |x: i32| {
total += x;
total
};
assert_eq!(accumulate(5), 5);
assert_eq!(accumulate(5), 10);
// move:拿走所有权(线程场景必需)
let owned = String::from("我的数据");
let show = move || println!("闭包拿到了:{owned}");
show();
// owned 已被移动,这里不能再用
}
迭代器链:适配器 + 消费者:
fn main() {
let nums = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 偶数的平方求和:一条链读完
let sum: i32 = nums
.iter()
.filter(|n| *n % 2 == 0)
.map(|n| n * n)
.sum();
assert_eq!(sum, 220);
// collect 把迭代器收成 Vec
let labels: Vec<String> = nums
.iter()
.take(3)
.enumerate()
.map(|(i, n)| format!("第{}项:{n}", i + 1))
.collect();
assert_eq!(labels, vec!["第1项:1", "第2项:2", "第3项:3"]);
// find / any / all 这类短路消费者也好用
assert!(nums.iter().any(|n| *n > 9));
assert!(nums.iter().all(|n| *n > 0));
println!("迭代器链全部验证通过");
}
常见坑
- map 之后没有消费者:v.iter().map(...) 单独一行不执行任何操作,编译器会警告 must be used;收尾用 collect/sum/for_each。
- 闭包里修改环境忘了 mut:闭包变量本身要 let mut,且捕获期与外部的其他借用不能冲突——和普通借用规则一致。
- move 之后还想用原值:move 闭包拿走所有权,原变量失效;只想借用就别写 move(线程场景例外,必须 move)。
- 迭代中修改原集合:借用规则禁止;模式是「迭代旧的、collect 新的、替换」或者先收集索引再改。
小结
闭包 = 捕获环境的匿名函数,三种捕获强度;迭代器 = 惰性适配器 + 消费者,零成本抽象放心链。下一章看能突破编译期借用限制的三个智能指针。