第八章:迭代器与闭包——函数式的优雅

第八章:迭代器与闭包——函数式的优雅

本章导读: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 的迭代器和闭包:

  • 闭包是匿名函数,可以捕获环境变量
  • 三种捕获 traitFn(不可变借用)、FnMut(可变借用)、FnOnce(获取所有权)
  • <code>move</code> 强制闭包获取所有权,常用于多线程
  • 迭代器提供统一的序列遍历接口
  • 消费适配器消耗迭代器返回值(sumcollectfold
  • 迭代适配器返回新迭代器,支持链式调用(mapfiltertake
  • 惰性求值意味着迭代器方法不会执行直到被消费
  • 零成本抽象意味着高级语法不牺牲性能

关键要点:

  • 优先使用迭代器方法而非显式循环
  • 利用链式调用表达数据处理管道
  • 实现自定义迭代器让类型更易用

在下一章,我们将深入生命周期——这是理解复杂 Rust 代码的关键。


动手实验

  1. 使用迭代器方法实现:找出一个字符串中最长的单词(提示:使用 splitwhitespace</code> 和 <code>maxby_key)。
  2. 实现一个自定义迭代器 evens,从任意起点开始生成偶数序列。
  3. 使用 fold 实现一个计算字符串中各字符出现次数的函数,返回 HashMap<char, usize>
← 返回目录