输入输出:文件、控制台与错误边界
8 / 11
Cargo 与 crates.io
自在学
分类课程AI导师创意工坊价格
分类课程AI导师创意工坊价格
编程Rust迭代器

迭代器

迭代器是 Rust 中处理数据的一种强大工具,就像流水线一样,可以让数据一步步经过不同的处理环节。 它有两个重要特点:一是“懒惰”——只有在真正需要结果时才开始工作;二是可以任意组合——我们可以把多个处理步骤串联起来。

Rust语言


三种入口

这三个方法分别对应集合的不同借用方式,理解它们的区别是掌握迭代器的第一步:

  • iter():创建一个对集合元素的不可变引用迭代器,原集合保持不变,可以继续使用
  • iter_mut():创建一个对集合元素的可变引用迭代器,可以修改原集合中的元素
  • into_iter():消费集合并获得所有权,创建一个拥有元素的迭代器

让我们通过一个例子来看看它们的区别。假设我们有一个数字向量,想要进行不同的操作:

|
fn main() { let mut v = vec![1, 2, 3]; let sum_readonly: i32 = v.iter().copied().sum(); for x in v.iter_mut() { *x *= 2; } let collected: Vec<_> = v.into_iter().map(|x| x + 1).collect(); println!("sum={sum_readonly}, collected={:?}", collected); }
|
sum=6, collected=[3, 5, 7]

适配器与消费器

在 Rust 的迭代器系统中,我们需要理解两个核心概念:适配器和消费器。它们就像工厂流水线中的不同环节。

适配器(Iterator Adaptors) 是数据的"变形师",它们接收一个迭代器,对其进行某种变换,然后返回一个新的迭代器。重要的是,适配器本身是懒惰的——它们不会立即执行,只是描述了要做什么:

  • map():对每个元素应用函数进行转换
  • filter():根据条件筛选元素
  • flat_map():先映射再展平,处理嵌套结构
  • scan():带状态的映射,类似累积器
  • take():只取前 n 个元素
  • skip():跳过前 n 个元素
  • inspect():不改变数据,但可以观察每个元素(常用于调试)

消费器(Consumers) 则是真正的"执行者",它们会消费迭代器并产生最终结果。只有调用消费器,整个迭代器链才会开始工作:

  • collect():收集所有元素到集合中
  • sum()/product():计算总和或乘积
  • for_each():对每个元素执行副作用操作
  • fold()/reduce():累积计算
  • all()/any():检查是否所有/任意元素满足条件

通过巧妙组合适配器和消费器,我们可以构建出表达力极强的数据处理流水线,代码既简洁又高效。

让我们通过一个例子来看看适配器和消费器的区别。假设我们有一个单词列表,想要进行不同的操作:

|
fn main() { let words = ["rust", "is", "fast"]; let upper_joined = words .iter() .map(|w| w.to_uppercase()) .filter(|w| w.len() > 2) .inspect(|w| eprintln!("debug: {}", w)) .collect::<Vec<_>>() .join(","); println!("{upper_joined}"); }
|
debug: RUST debug: FAST RUST,FAST

从迭代器构造集合

Rust 的迭代器系统有一个非常优雅的设计:通过 collect() 方法,我们可以将任何迭代器转换成各种标准集合类型。这背后的魔法来自于 FromIterator trait——所有标准集合都实现了这个 trait,使得它们能够从迭代器中构建自己的实例。

collect() 方法非常智能,它可以根据上下文自动推断目标类型。当 Rust 编译器无法确定具体类型时,我们可以使用类型注解来明确指定。最常见的方式是使用"涡轮鱼"语法(turbofish syntax)::<Type>,或者在变量声明时指定类型。

让我们通过一个例子来看看如何从迭代器构造集合。

|
use std::collections::{HashMap, HashSet}; fn main() { let set: HashSet<_> = (0..5).map(|x| x * 2).collect(); let map: HashMap<_, _> = ["a", "b"].into_iter().enumerate().collect(); println!("set={:?}, map={:?}", set, map); }

链式与短路

在迭代器的世界中,有一类特殊的消费器被称为“短路消费器”,它们具有一个重要特性:当遇到特定条件时会立即停止迭代,而不是处理完所有元素。

常见的短路消费器包括:

  • find() 方法:寻找第一个满足条件的元素。一旦找到匹配项,它就会立即返回 Some(element),不会继续检查后续元素。如果遍历完所有元素都没有找到匹配项,则返回 None。
  • any() 方法:检查是否有任意一个元素满足条件。只要找到一个满足条件的元素,就立即返回 true,无需检查剩余元素。只有当所有元素都不满足条件时才返回 false。
  • all() 方法:检查是否所有元素都满足条件。一旦发现有元素不满足条件,就立即返回 false,不会继续检查后续元素。只有当所有元素都满足条件时才返回 true。
  • position() 方法:寻找第一个满足条件的元素的位置索引。找到后立即返回 Some(index),否则返回 None。
  • take_while() 方法:从迭代器开头取元素,直到遇到第一个不满足条件的元素为止。这是一个适配器方法,它会在条件不满足时停止生成新元素。

这种短路行为在处理大型数据集或无限迭代器时特别有价值,因为它避免了不必要的计算开销,让我们的程序既快速又节省资源。

看一个简单的例子:

|
fn main() { let idx = (0..).take(1000).position(|x| x % 37 == 0 && x > 0).unwrap(); println!("idx={idx}"); }

上面的例子中,我们创建了一个无限迭代器 (0..),然后使用 take(1000) 限制其长度为 1000,再使用 position 方法找到第一个满足条件的元素的位置。由于我们在条件中添加了 x > 0,所以第一个满足条件的元素是 37。


自定义迭代器:实现 Iterator

在 Rust 中,任何类型都可以通过实现 Iterator trait 来成为迭代器。这个 trait 的核心非常简单——只需要定义一个 next() 方法,该方法返回 Option<Self::Item>。 当有下一个元素时返回 Some(item),当迭代结束时返回 None。

Iterator trait 的定义大致如下:

|
struct Stepper { current: i32, end: i32, step: i32 } impl Stepper { fn new(start: i32, end: i32, step: i32) -> Self { Self { current: start, end, step } } } impl Iterator for Stepper { type Item = i32; fn next(&mut self) -> Option<Self::Item> { if self.current > self.end { return None; } let v = self.current; self.current += self.step; Some(v) } } fn main() { println!("{:?}", Stepper::new(0, 5, 2).map(|x| x * 3).collect::<Vec<_>>()); }
|
[0, 6, 12]

适配器实践:scan 与状态机

scan 方法是一个强大的适配器,它允许我们在迭代过程中维护和更新一个内部状态。与 map 不同的是,scan 不仅可以转换每个元素,还能在处理过程中累积信息,这使得它特别适合构建轻量级的状态机或实现需要记忆前面处理结果的算法。

scan 的工作原理是:它接受一个初始状态和一个闭包函数,闭包函数会接收到当前状态的可变引用和当前元素,然后返回一个 Option。如果返回 Some(value),则 value 会被添加到结果迭代器中,状态也会被更新;如果返回 None,则迭代提前结束。

这种设计让 scan 成为处理累积计算、滑动窗口、状态跟踪等场景的理想工具。我们可以把它想象成一个带记忆的 map 操作,每次处理都能"记住"之前的计算结果。

|
fn main() { let prefix_sum: Vec<i32> = [1,2,3,4] .into_iter() .scan(0, |acc, x| { *acc += x; Some(*acc) }) .collect(); println!("{:?}", prefix_sum); }
|
[1, 3, 6, 10]

小练习

  1. Rust中用于创建迭代器的方法包括?
  1. 使用iter()创建的迭代器可以?
  1. Rust迭代器中常用的方法包括?
  1. collect()方法属于?
  1. Rust中哪些方法是短路消费器?
  1. 在Rust中,实现哪个trait可以让类型成为迭代器?
  1. scan()方法的特点包括?
  1. fold()方法的主要用途是?

9. 三种迭代器入口练习

给定一个可变向量 vec![1, 2, 3],完成以下三个任务:

  • 任务A:使用 iter() 创建只读迭代器,计算所有元素的和(使用 copied() 将引用转换为值,然后使用 sum())。注意:使用 iter() 后,原向量 v 仍然可以继续使用。
  • 任务B:使用 iter_mut() 创建可变迭代器,在循环中将每个元素翻倍(使用 *x *= 2)。注意:iter_mut() 允许修改原集合中的元素。
  • 任务C:使用 into_iter() 消费向量,创建一个新向量,其中每个元素都减1(使用 map(|x| x - 1) 和 collect())。注意:使用 into_iter() 后,原向量 v 不能再使用。
|
fn main() { // A. 只读求和(保持 v 可继续使用) let mut v = vec![1, 2, 3]; let sum: i32 = v.iter() // iter()创建不可变引用迭代器 .copied() // 将&i32转换为i32 .sum(); // 求和 assert_eq!(sum, 6); println!("sum={}", sum); // B. 原地翻倍(允许修改原集合) for x in v.iter_mut() { // iter_mut()创建可变引用迭代器 *x *= 2; // 修改元素 } assert_eq!(v, vec![2, 4, 6]); println!("v={:?}", v); // C. 消费集合并返回新集合 let collected: Vec<_> = v.into_iter() // into_iter()消费集合 .map(|x| x - 1) // 每个元素减1 .collect(); // 收集为新向量 assert_eq!(collected, vec![1, 3, 5]); println!("collected={:?}", collected); }
|
sum=6 v=[2, 4, 6] collected=[1, 3, 5]

说明:

  • iter() 创建不可变引用迭代器,不消费集合,可以继续使用原集合
  • iter_mut() 创建可变引用迭代器,可以修改元素,但不消费集合
  • into_iter() 消费集合,获得所有权,使用后原集合不能再使用
  • copied() 将引用转换为值,用于 iter() 迭代器
  • sum() 是消费器,计算所有元素的和

10. 链式迭代器操作练习

使用链式迭代器操作,完成以下任务:

  • 从范围 1..=20 开始
  • 筛选出所有奇数(使用 filter(|x| x % 2 != 0))
  • 对每个奇数取平方(使用 map(|x| x * x))
  • 跳过前3个结果(使用 skip(3))
  • 只取接下来的4个元素(使用 take(4))
  • 收集结果到 Vec<i32>
  • 最终结果应该是 [121, 169, 225, 289]

提示:奇数序列的平方是 [1, 9, 25, 49, 81, 121, 169, 225, 289, ...],跳过前3个后取4个就是 [121, 169, 225, 289]。

|
fn main() { let v: Vec<i32> = (1..=20) .filter(|x| x % 2 != 0) // 筛选奇数 .map(|x| x * x) // 取平方 .skip(3) // 跳过前3个 .take(4) // 取接下来的4个 .collect(); assert_eq!(v, vec![121, 169, 225, 289]); println!("{:?}", v); }
|
[121, 169, 225, 289]

说明:

  • filter() 用于筛选满足条件的元素
  • map() 用于转换每个元素
  • skip() 跳过前n个元素
  • take() 只取前n个元素
  • 这些适配器可以链式调用,形成数据处理流水线
  • 只有调用 collect() 时,整个迭代器链才会执行

11. 集合构造练习

完成以下两个任务:

  • 任务A:将单词数组 ["rust", "rust", "book", "fast"] 去重后收集为 HashSet。注意:数组需要先转换为迭代器(使用 .iter() 或 .into_iter()),然后使用 collect() 收集到 HashSet。
  • 任务B:将单词数组转换为 HashMap<usize, usize>,其中键是索引,值是单词长度。使用 enumerate() 获取索引,使用 map() 将每个元素转换为 (索引, 长度) 元组,然后使用 collect() 收集。
|
use std::collections::{HashMap, HashSet}; fn main() { let words = ["rust", "rust", "book", "fast"]; // 任务A:去重后收集为HashSet let set: HashSet<_> = words.into_iter().collect(); assert!(set.contains("rust") && set.contains("book") && set.contains("fast")); println!("set={:?}", set); // 任务B:收集为(索引, 长度)的HashMap let lengths: HashMap<usize, usize> = words .into_iter() .enumerate() // 获取(索引, 值)元组 .map(|(i, w)| (i, w.len())) // 转换为(索引, 长度) .collect(); assert_eq!(lengths.get(&0), Some(&4)); println!("lengths={:?}", lengths); }
|
set={"rust", "book", "fast"} lengths={0: 4, 1: 4, 2: 4, 3: 4}

说明:

  • HashSet 自动去重,重复的元素只会保留一个
  • enumerate() 为迭代器添加索引,返回 (usize, Item) 元组
  • collect() 可以根据目标类型自动推断如何收集
  • 使用类型注解(如 HashSet<_>)帮助编译器推断类型

12. 短路消费器练习

完成以下两个任务:

  • 任务A:在无限范围 1.. 中寻找第一个同时被 7 和 11 整除的正数。使用 find() 方法,条件是两个数都能整除(x % 7 == 0 && x % 11 == 0)。注意:find() 是短路消费器,找到第一个满足条件的元素后就会停止。
  • 任务B:判断范围 1..100 中是否存在平方为偶数的奇数。使用 any() 方法,条件是 x 为奇数(x % 2 != 0)且 x * x 为偶数((x * x) % 2 == 0)。注意:奇数的平方永远是奇数,所以结果应该是 false。
|
fn main() { // 任务A:寻找第一个同时被7和11整除的数 let n = (1..) .find(|x| x % 7 == 0 && x % 11 == 0) // 找到第一个满足条件的元素 .unwrap(); // 解包Option assert_eq!(n, 77); println!("n={}", n); // 任务B:判断是否存在平方为偶数的奇数 let exists = (1..100) .any(|x| x % 2 != 0 && (x * x) % 2 == 0); // 检查是否存在满足条件的元素 assert!(!exists); // 奇数的平方永远是奇数,所以应该是false println!("exists={}", exists); }
|
n=77 exists=false

说明:

  • find() 是短路消费器,找到第一个满足条件的元素后立即返回 Some(element)
  • any() 是短路消费器,只要找到一个满足条件的元素就返回 true
  • 对于无限迭代器,短路行为特别重要,避免无限循环
  • 奇数的平方永远是奇数,所以不存在平方为偶数的奇数

13. 自定义迭代器实现练习

实现一个 Fib 结构体,它实现 Iterator trait,用于生成斐波那契数列。

要求:

  • Fib 结构体包含两个字段:a: u64 和 b: u64,分别表示当前和前一个斐波那契数
  • new() 方法创建初始状态:a = 0, b = 1
  • next() 方法实现迭代逻辑:
    • 返回当前的 a 值(包装在 Some 中)
    • 更新状态:(a, b) = (b, a + b),即下一个斐波那契数是前两个数的和
    • 注意:需要先保存当前 a 的值用于返回,然后更新状态

在 main 函数中,使用 Fib::new().take(10).collect() 收集前10个斐波那契数,验证结果是 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]。

|
struct Fib { a: u64, b: u64 } impl Fib { fn new() -> Self { Self { a: 0, b: 1 } } } impl Iterator for Fib { type Item = u64; fn next(&mut self) -> Option<Self::Item> { let current = self.a; // 保存当前值用于返回 let next = self.a + self.b; // 计算下一个斐波那契数 self.a = self.b; // 更新a为原来的b self.b = next; // 更新b为下一个数 Some(current) // 返回当前值 } } fn main() { let first_ten: Vec<u64> = Fib::new().take(10).collect(); assert_eq!(first_ten, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); println!("{:?}", first_ten); }
|
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

说明:

  • Iterator trait 需要定义 type Item 和 next() 方法
  • next() 返回 Option<Self::Item>,有下一个元素时返回 Some(item),迭代结束时返回 None
  • 斐波那契数列的递推关系:F(n) = F(n-1) + F(n-2)
  • 状态更新顺序很重要:先保存当前值,再计算下一个值,最后更新状态
  • 自定义迭代器可以与其他迭代器适配器(如 take())组合使用

14. scan适配器练习

使用 scan() 方法计算数组 [2, 3, 5, 7, 11] 的前缀和,但有一个特殊要求:一旦前缀和超过 10 就停止产生新元素。

要求:

  • 使用 scan() 方法维护一个累加器(初始值为 0)
  • 对于每个元素,将累加器加上当前元素的值
  • 如果累加后的值超过 10,返回 None 提前结束迭代
  • 如果累加后的值不超过 10,返回 Some(累加值) 并更新累加器
  • 最终结果应该是 [2, 5, 10](2, 2+3=5, 5+5=10,下一个 10+7=17 超过10,停止)
|
fn main() { let data = [2, 3, 5, 7, 11]; let prefix: Vec<i32> = data .into_iter() .scan(0, |acc, x| { // scan维护累加器,初始值为0 *acc += x; // 累加当前元素 if *acc > 10 { // 如果超过10 None // 返回None,提前结束迭代 } else { Some(*acc) // 否则返回当前累加值 } }) .collect(); assert_eq!(prefix, vec![2, 5, 10]); println!("{:?}", prefix); }
|
[2, 5, 10]

说明:

  • scan() 接受一个初始状态和一个闭包函数
  • 闭包函数接收状态的可变引用 &mut State 和当前元素,返回 Option<Output>
  • 返回 Some(value) 时,value 会被添加到结果迭代器中,状态也会被更新
  • 返回 None 时,迭代会提前结束
  • scan() 特别适合实现带状态的累积计算和提前终止的逻辑

15. fold累积操作练习

使用 fold() 方法统计字符串数组 ["rust", "fast", "rust", "safe", "fast", "rust"] 中每个单词的出现次数,将结果收集到 HashMap<&str, usize> 中。

要求:

  • 使用 fold() 方法,初始值为 HashMap::new()
  • 对于每个单词,在 HashMap 中查找该单词的计数
  • 如果单词不存在,插入计数 1
  • 如果单词已存在,将计数加 1
  • 可以使用 entry() API 或 get() + insert() 的方式
  • 最终结果:"rust" 出现 3 次,"fast" 出现 2 次,"safe" 出现 1 次
|
use std::collections::HashMap; fn main() { let words = ["rust", "fast", "rust", "safe", "fast", "rust"]; let counts: HashMap<&str, usize> = words .into_iter() .fold(HashMap::new(), |mut map, word| { // fold累积,初始值为空HashMap *map.entry(word).or_insert(0) += 1; // 使用entry API更新计数 map // 返回更新后的HashMap }); assert_eq!(counts.get("rust"), Some(&3)); assert_eq!(counts.get("fast"), Some(&2)); assert_eq!(counts.get("safe"), Some(&1)); println!("{:?}", counts); }
|
{"rust": 3, "fast": 2, "safe": 1}

说明:

  • fold() 接受一个初始值和一个闭包函数
  • 闭包函数接收累积值(这里是 HashMap)和当前元素,返回新的累积值
  • entry() API 提供了优雅的方式来处理"存在则更新,不存在则插入"的逻辑
  • or_insert(0) 如果键不存在则插入 0,然后返回值的可变引用
  • fold() 是通用的累积操作,可以用于实现各种聚合功能,如求和、计数、分组等
  • 三种入口
  • 适配器与消费器
  • 从迭代器构造集合
  • 链式与短路
  • 自定义迭代器:实现 `Iterator`
  • 适配器实践:`scan` 与状态机
  • 小练习

目录

  • 三种入口
  • 适配器与消费器
  • 从迭代器构造集合
  • 链式与短路
  • 自定义迭代器:实现 `Iterator`
  • 适配器实践:`scan` 与状态机
  • 小练习
自在学

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1