第八章:迭代器与闭包——函数式的优雅
本章导读:Rust 从函数式编程中汲取了丰富的营养,迭代器和闭包就是最好的证明。迭代器让你以声明式的方式处理序列数据,闭包让你捕获和传递代码块。两者结合,可以写出既简洁又高效的代码。本章将揭示 Rust 如何实现"零成本抽象"——高级语法背后是编译器的极致优化。
🔒 8.1 闭包:可以保存的代码
闭包是匿名函数,可以捕获其环境中的变量。
📝 8.1.1 基本语法
fn main() {
// 普通函数
fn add_one(x: i32) -> i32 {
x + 1
}
// 闭包:类型推断
let add_one_closure = |x| x + 1;
println!("函数: {}", add_one(5));
println!("闭包: {}", add_one_closure(5));
// 多行闭包
let complex = |x| {
let doubled = x * 2;
let squared = doubled * doubled;
squared + 1
};
// 带类型标注
let typed: fn(i32) -> i32 = |x: i32| -> i32 { x + 1 };
}
🎣 8.1.2 捕获环境变量
闭包的独特之处在于可以捕获定义作用域中的变量:
fn main() {
let multiplier = 3;
// 捕获 multiplier
let multiply = |x| x * multiplier;
println!("{}", multiply(5)); // 15
// 捕获并修改
let mut counter = 0;
let mut increment = || {
counter += 1;
counter
};
println!("{}", increment()); // 1
println!("{}", increment()); // 2
println!("Counter: {}", counter); // 2
}
🏷️ 8.1.3 三种捕获方式
闭包通过三种 trait 捕获变量:
// FnOnce:获取所有权,只能调用一次
fn consume<F>(f: F)
where
F: FnOnce()
{
f(); // f 被消耗
// f(); // 错误!不能再次调用
}
// FnMut:可变借用,可以多次调用,可以修改捕获的变量
fn modify<F>(mut f: F)
where
F: FnMut(),
{
f();
f();
}
// Fn:不可变借用,可以多次调用,不能修改
fn read<F>(f: F)
where
F: Fn() -> i32,
{
println!("{}", f());
println!("{}", f());
}
fn main() {
let mut value = 10;
// FnOnce:移动捕获
let consume_closure = move || {
println!("{}", value); // value 被移动
};
consume(consume_closure);
// FnMut:可变借用
let mut x = 0;
let mut increment = || {
x += 1;
x
};
modify(|| {
increment();
});
// Fn:不可变借用
let y = 5;
let read_closure = || y;
read(read_closure);
}
🚚 8.1.4 move 关键字
move 强制闭包获取捕获变量的所有权:
fn main() {
let name = String::from("张三");
// 不使用 move:name 被借用
let greet = || println!("你好, {}", name);
greet();
println!("name 仍然有效: {}", name);
// 使用 move:name 被移动
let greet_moved = move || println!("再见, {}", name);
greet_moved();
// println!("{}", name); // 错误!name 已被移动
}
move 在将闭包传递给新线程时特别有用:
use std::thread;
fn main() {
let data = vec![1, 2, 3, 4, 5];
// 必须使用 move,因为新线程可能比当前作用域活得更长
thread::spawn(move || {
for n in data {
println!("{}", n);
}
}).join().unwrap();
}
🔄 8.2 迭代器:序列的抽象
迭代器提供了一种统一的方式来遍历序列,无需关心底层数据结构。
🎯 8.2.1 Iterator Trait
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 许多默认方法...
}
只需要实现 next() 方法,就能获得丰富的迭代器方法。
📖 8.2.2 基本用法
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 创建迭代器
let mut iter = v.iter();
// 手动调用 next
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), Some(&4));
assert_eq!(iter.next(), Some(&5));
assert_eq!(iter.next(), None);
// for 循环使用迭代器
for val in v.iter() {
println!("{}", val);
}
// 直接遍历(v 被借用)
for val in &v {
println!("{}", val);
}
// 消费迭代器(v 被移动)
for val in v {
println!("{}", val);
}
// v 不再可用
}
🔧 8.2.3 消费适配器
消费适配器消耗迭代器,返回一个值:
fn main() {
let v = vec![1, 2, 3, 4, 5];
// sum:求和
let sum: i32 = v.iter().sum();
println!("Sum: {}", sum); // 15
// product:求积
let product: i32 = v.iter().product();
println!("Product: {}", product); // 120
// count:计数
let count = v.iter().count();
println!("Count: {}", count); // 5
// collect:收集到集合
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
println!("Doubled: {:?}", doubled); // [2, 4, 6, 8, 10]
// fold:累加器
let sum_with_initial = v.iter().fold(10, |acc, &x| acc + x);
println!("Fold: {}", sum_with_initial); // 25
// reduce:不带初始值的 fold
let max = v.iter().reduce(|a, &b| if a > &b { a } else { &b });
println!("Max: {:?}", max); // Some(5)
// any, all:存在/全部满足条件
let has_even = v.iter().any(|&x| x % 2 == 0);
let all_positive = v.iter().all(|&x| x > 0);
println!("Has even: {}, All positive: {}", has_even, all_positive);
// find:找到第一个满足条件的元素
let first_even = v.iter().find(|&&x| x % 2 == 0);
println!("First even: {:?}", first_even); // Some(2)
// position:找到第一个满足条件的位置
let pos = v.iter().position(|&x| x == 3);
println!("Position of 3: {:?}", pos); // Some(2)
}
🔌 8.2.4 迭代适配器
迭代适配器返回新的迭代器,支持链式调用:
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map:转换每个元素
let squared: Vec<i32> = v.iter().map(|&x| x * x).collect();
println!("Squared: {:?}", squared);
// filter:过滤元素
let evens: Vec<&i32> = v.iter().filter(|&&x| x % 2 == 0).collect();
println!("Evens: {:?}", evens);
// filter_map:过滤并转换(None 被过滤掉)
let parsed: Vec<i32> = vec!["1", "2", "three", "4"]
.iter()
.filter_map(|s| s.parse::<i32>().ok())
.collect();
println!("Parsed: {:?}", parsed); // [1, 2, 4]
// take:取前 n 个
let first_three: Vec<&i32> = v.iter().take(3).collect();
println!("First 3: {:?}", first_three);
// skip:跳过前 n 个
let after_three: Vec<&i32> = v.iter().skip(3).collect();
println!("After 3: {:?}", after_three);
// take_while:在条件为真时取值
let less_than_five: Vec<&i32> = v.iter().take_while(|&&x| x < 5).collect();
// skip_while:在条件为真时跳过
let from_five: Vec<&i32> = v.iter().skip_while(|&&x| x < 5).collect();
// chain:连接两个迭代器
let combined: Vec<&i32> = v.iter().chain(vec![11, 12].iter()).collect();
// zip:配对两个迭代器
let names = vec!["Alice", "Bob", "Charlie"];
let scores = vec![90, 85, 95];
let pairs: Vec<(&str, i32)> = names.iter().zip(scores.iter())
.map(|(&name, &score)| (name, score))
.collect();
// enumerate:添加索引
for (i, &val) in v.iter().enumerate() {
println!("Index {}: {}", i, val);
}
// peekable:可以预览下一个元素
let mut peekable = v.iter().peekable();
if let Some(&&first) = peekable.peek() {
println!("First is {}", first);
}
// inspect:查看中间值(调试用)
let result: Vec<i32> = v.iter()
.inspect(|&&x| println!("Processing {}", x))
.map(|&x| x * 2)
.inspect(|&x| println!("After map {}", x))
.collect();
}
🔄 8.2.5 惰性求值
迭代器是惰性的——它们不会做任何工作直到被消费:
fn main() {
let v = vec![1, 2, 3];
// 这行代码什么都不会输出!
v.iter().map(|x| {
println!("Processing {}", x); // 不会执行
x * 2
});
// 只有消费迭代器时才执行
let _: Vec<i32> = v.iter().map(|x| {
println!("Processing {}", x); // 会执行三次
x * 2
}).collect();
}
⚡ 8.3 零成本抽象
Rust 的迭代器是"零成本抽象"的典范——高级语法编译成高效的机器码。
🔬 8.3.1 循环融合
// 高级写法
let sum: i32 = (1..=100)
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.sum();
// 编译器优化后类似于:
let mut sum = 0;
for x in 1..=100 {
if x % 2 == 0 {
sum += x * x;
}
}
多个迭代器方法被"融合"成单个循环,没有中间集合分配。
📊 8.3.2 与手写循环的比较
fn main() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 手写循环
let mut sum_loop = 0;
for &x in &data {
if x % 2 == 0 {
sum_loop += x * x;
}
}
// 迭代器
let sum_iter: i32 = data.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * x)
.sum();
assert_eq!(sum_loop, sum_iter);
}
在 release 模式下,两种写法的性能几乎相同。迭代器版本更具表达力,同时不牺牲效率。
🧪 8.4 实战:数据处理管道
让我们用迭代器和闭包构建一个数据处理管道:
use std::fs;
#[derive(Debug)]
struct Student {
name: String,
score: u32,
grade: String,
}
impl Student {
fn from_line(line: &str) -> Option<Self> {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() != 3 {
return None;
}
let score = parts[1].trim().parse().ok()?;
Some(Student {
name: parts[0].trim().to_string(),
score,
grade: parts[2].trim().to_string(),
})
}
fn passed(&self) -> bool {
self.score >= 60
}
fn is_excellent(&self) -> bool {
self.score >= 90
}
}
fn analyze_students(file_path: &str) {
let content = match fs::read_to_string(file_path) {
Ok(c) => c,
Err(_) => {
println!("无法读取文件,使用示例数据");
r#"
张三, 85, A
李四, 92, A
王五, 58, C
赵六, 73, B
钱七, 95, A
孙八, 45, F
"#.to_string()
}
};
// 解析并过滤有效数据
let students: Vec<Student> = content
.lines()
.filter(|line| !line.trim().is_empty())
.filter_map(Student::from_line)
.collect();
// 统计通过人数
let passed_count = students.iter().filter(|s| s.passed()).count();
let total_count = students.len();
// 优秀学生名单
let excellent: Vec<&Student> = students
.iter()
.filter(|s| s.is_excellent())
.collect();
// 按分数排序
let mut sorted: Vec<&Student> = students.iter().collect();
sorted.sort_by(|a, b| b.score.cmp(&a.score));
// 平均分
let avg_score = students.iter().map(|s| s.score).sum::<u32>() as f64 / total_count as f64;
// 各成绩等级人数
let grade_counts: std::collections::HashMap<&str, usize> = students
.iter()
.fold(std::collections::HashMap::new(), |mut acc, s| {
*acc.entry(s.grade.as_str()).or_insert(0) += 1;
acc
});
println!("=== 学生成绩分析 ===");
println!("总人数: {}", total_count);
println!("通过人数: {} ({:.1}%)", passed_count, passed_count as f64 / total_count as f64 * 100.0);
println!("平均分: {:.1}", avg_score);
println!("\n优秀学生 ({})人:", excellent.len());
for s in &excellent {
println!(" {} - {} 分", s.name, s.score);
}
println!("\n成绩排名:");
for (i, s) in sorted.iter().enumerate() {
let medal = match i {
0 => "🥇",
1 => "🥈",
2 => "🥉",
_ => "",
};
println!("{}{} {} - {} 分 ({})", i + 1, medal, s.name, s.score, s.grade);
}
println!("\n各等级人数:");
for (grade, count) in &grade_counts {
println!(" {}: {} 人", grade, count);
}
}
fn main() {
analyze_students("students.csv");
}
🔧 8.5 创建自定义迭代器
实现 Iterator trait 可以让任何类型变成可迭代的:
// 斐波那契迭代器
struct Fibonacci {
current: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
Self { current: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current;
self.current = self.next;
self.next = current + self.next;
Some(current)
}
}
fn main() {
// 获取前 10 个斐波那契数
let fibs: Vec<u64> = Fibonacci::new().take(10).collect();
println!("前 10 个斐波那契数: {:?}", fibs);
// 找到第一个超过 1000 的斐波那契数
let first_big = Fibonacci::new()
.find(|&n| n > 1000);
println!("第一个超过 1000 的: {:?}", first_big);
}
// 带结束条件的迭代器
struct Counter {
count: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Self { count: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
📝 本章小结
本章我们学习了 Rust 的迭代器和闭包:
- 闭包是匿名函数,可以捕获环境变量
- 三种捕获 trait:
Fn(不可变借用)、FnMut(可变借用)、FnOnce(获取所有权) - <code>move</code> 强制闭包获取所有权,常用于多线程
- 迭代器提供统一的序列遍历接口
- 消费适配器消耗迭代器返回值(
sum、collect、fold) - 迭代适配器返回新迭代器,支持链式调用(
map、filter、take) - 惰性求值意味着迭代器方法不会执行直到被消费
- 零成本抽象意味着高级语法不牺牲性能
关键要点:
- 优先使用迭代器方法而非显式循环
- 利用链式调用表达数据处理管道
- 实现自定义迭代器让类型更易用
在下一章,我们将深入生命周期——这是理解复杂 Rust 代码的关键。
动手实验:
- 使用迭代器方法实现:找出一个字符串中最长的单词(提示:使用
splitwhitespace</code> 和 <code>maxby_key)。- 实现一个自定义迭代器
evens,从任意起点开始生成偶数序列。- 使用
fold实现一个计算字符串中各字符出现次数的函数,返回HashMap<char, usize>。