深入浅出 Rust 迭代器:从基础用法到性能优化
在 Rust 开发中,迭代器(Iterator)不仅是遍历集合的工具,更是 Rust 函数式编程风格的基石,也是零成本抽象理念的典型体现。本文将从基础用法入手,逐步深入迭代器的核心特性、高级用法,再到性能优化技巧,帮助你真正掌握 Rust 迭代器。
什么是迭代器
简单来说,迭代器是一种可以依次访问集合(如 Vec、Slice、HashSet 等)中元素的工具,它封装了"遍历逻辑",让我们无需手动管理索引(避免越界风险),就能高效遍历数据。
下面我们可以通过集合的 iter() 方法创建迭代器,然后手动调用 next() 遍历,也可以使用 for 循环(Rust 的 for 循环本质就是对迭代器的封装):
rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 手动调用 next() 遍历
let mut iter = v.iter();
println!("{:?}", iter.next()); // Some(&1)
println!("{:?}", iter.next()); // Some(&2)
println!("{:?}", iter.next()); // Some(&3)
println!("{:?}", iter.next()); // Some(&4)
println!("{:?}", iter.next()); // Some(&5)
println!("{:?}", iter.next()); // None
// for 循环遍历(推荐用法)
for item in v.iter() {
println!("{}", item); // 依次打印 1-5
}
}
Rust 为集合提供了三种常用的迭代器创建方法,对应不同的所有权语义,这是 Rust 迭代器的核心特点之一,也是避免内存安全问题的关键:
iter():创建不可变借用迭代器,返回元素的不可变引用(&T),不获取元素所有权,集合本身不受影响。iter_mut():创建可变借用迭代器,返回元素的可变引用(&mut T),可以修改集合中的元素,同样不获取集合所有权。into_iter():创建消费型迭代器,获取元素的所有权(T),迭代结束后,原集合会被消耗(无法再使用)。
代码示例对比:
rust
fn main() {
let mut v = vec![1, 2, 3];
// iter():不可变借用,无法修改元素
for &item in v.iter() {
println!("不可变借用:{}", item);
}
println!("iter 后集合:{:?}", v); // 集合依然可用:[1,2,3]
// iter_mut():可变借用,可修改元素
for item in v.iter_mut() {
*item *= 2; // 修改元素值
}
println!("iter_mut 后集合:{:?}", v); // 集合被修改:[2,4,6]
// into_iter():消费型迭代,获取所有权
for item in v.into_iter() {
println!("消费型迭代:{}", item);
}
// println!("into_iter 后集合:{:?}", v); // 报错:v 已被消耗,无法访问
}
惰性求值与适配器
Rust 迭代器最强大的特性之一是惰性求值,创建迭代器、调用适配器方法时,并不会立即执行遍历和计算,只有当我们调用消费者方法(消耗迭代器的方法)时,才会真正执行逻辑。这种设计不仅节省内存,还能让我们链式调用多个适配器,构建简洁、高效的数据流处理逻辑。
下面的代码中,我们调用了 map() 适配器,但如果不调用消费者方法,map() 中的逻辑永远不会执行:
rust
fn main() {
let v = vec![1, 2, 3];
// 仅创建迭代器,不执行任何逻辑(无打印输出)
let mapped_iter = v.iter().map(|x| {
println!("执行 map:{}", x);
x * 2
});
// 调用 collect() 消费者方法,才会执行 map 逻辑
let result: Vec<_> = mapped_iter.collect();
println!("结果:{:?}", result); // 结果:[2,4,6]
}
Rust 常用的适配器有以下几种:
map:元素转换
map() 接收一个闭包,将迭代器的每个元素转换为另一种类型,返回一个新的迭代器。这里实现一个将数字转换为字符串和进行算术运算的示例:
rust
fn main() {
let numbers = vec![1, 2, 3, 4];
// 数字转字符串
let strs: Vec<_> = numbers.iter().map(|x| x.to_string()).collect();
println!("{:?}", strs); // ["1", "2", "3", "4"]
// 每个数字加 10
let added: Vec<_> = numbers.iter().map(|&x| x + 10).collect();
println!("{:?}", added); // [11, 12, 13, 14]
}
filter:元素过滤
filter() 接收一个闭包,闭包返回一个 bool,仅保留闭包返回 true 的元素。这里实现一个过滤出偶数的示例:
rust
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<_> = numbers.iter().filter(|&x| x % 2 == 0).collect();
println!("偶数:{:?}", evens); // [2, 4, 6]
}
filter_map:过滤+转换
filter_map() 是 map 和 filter 的结合体,闭包返回 Option<B>,当返回 Some(b) 时保留元素 b,返回 None 时丢弃元素,比单独使用 map+filter 更简洁高效。
rust
use std::str::FromStr;
fn main() {
let text = "1\nfrond .25 289\n3.1415 estuary\n";
// 解析以空格分割的字符串,过滤无法转为浮点数的内容
for number in text.split_whitespace().filter_map(|x| f64::from_str(x).ok()) {
println!("{:4.2}", number); // 输出:1.00、0.25、289.00、3.14
}
}
flat_map:展平嵌套迭代器
flat_map() 接收一个闭包,闭包返回一个可迭代对象(如 Vec、Slice),最终将所有嵌套的迭代器展平为一个单层迭代器。适用于处理嵌套结构,比如"将二维数组转为一维数组"。
rust
fn main() {
let nested_vec = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
// 展平二维向量为一维向量
let flat_vec: Vec<_> = nested_vec.into_iter().flat_map(|v| v).collect();
println!("展平后:{:?}", flat_vec); // [1, 2, 3, 4, 5, 6]
}
消费者方法:触发迭代执行
适配器返回新的迭代器,而消费者方法则会消耗迭代器,触发实际的遍历和计算,返回一个非迭代器的结果。常用的消费者方法有:
collect():将迭代器的结果收集到集合中(如 Vec、HashSet),需要显式指定集合类型(或通过类型推断)。sum():对迭代器中的数值元素求和,返回一个数值类型。fold():折叠迭代器,接收一个初始值和一个闭包,依次将元素与初始值进行运算,返回最终结果(类似累加、累乘)。for_each():对每个元素执行闭包操作,无返回值(类似 for 循环)。
rust
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// collect:收集到 Vec
let vec: Vec<_> = numbers.iter().collect();
// sum:求和
let total: i32 = numbers.iter().sum();
println!("总和:{}", total); // 15
// fold:累乘(初始值为 1,闭包逻辑:acc * x)
let product: i32 = numbers.iter().fold(1, |acc, &x| acc * x);
println!("乘积:{}", product); // 120
// for_each:遍历执行操作
numbers.iter().for_each(|&x| println!("元素:{}", x));
}
自定义迭代器
在 Rust 中迭代器都实现了 Iterator 特征(Trait),下面的示例是一个生成斐波那契数列的迭代器,代码如下所示:
rust
// 定义斐波那契迭代器结构体,存储迭代状态(前两个数)
struct Fibonacci {
a: u32,
b: u32,
}
// 为 Fibonacci 实现 Iterator 特征
impl Iterator for Fibonacci {
// 定义迭代器返回的元素类型
type Item = u32;
// 核心方法:实现 next(),控制迭代逻辑
fn next(&mut self) -> Option<Self::Item> {
// 保存当前要返回的斐波那契数(初始为 a=0, b=1,首次返回0)
let current = self.a;
// 更新状态:下一个数 = 前两个数之和,同时移动指针
self.a = self.b;
self.b = current + self.b;
// 斐波那契数列无限递增,此处返回 Some(current),永不返回 None
// 若需限制迭代次数,可添加条件判断返回 None
Some(current)
}
}
// 提供一个构造函数,初始化斐波那契迭代器(初始值 0, 1)
fn fibonacci() -> Fibonacci {
Fibonacci { a: 0, b: 1 }
}
fn main() {
// 使用自定义迭代器,取前 10 个斐波那契数
let fib_iter = fibonacci().take(10);
// 收集并打印结果
let fibs: Vec<u32> = fib_iter.collect();
println!("前10个斐波那契数:{:?}", fibs);
// 输出:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
}
迭代器实战
掌握了迭代器的基础和核心特性后,我们可以用它来简化各种日常开发场景,替代繁琐的手动循环,让代码更简洁、更安全。
场景一:处理文本数据
需求:读取一段文本,去除每行的空格,过滤掉空行,然后将每行转为大写并收集到向量中。
rust
fn main() {
let text = " ponies \n giraffes\niguanas \n squid \n\n rust ";
let result: Vec<_> = text
.lines() // 按行分割,返回迭代器
.map(str::trim) // 去除每行前后空格
.filter(|s| !s.is_empty()) // 过滤空行
.map(str::to_uppercase) // 转为大写
.collect();
println!("{:?}", result);
// ["PONIES", "GIRAFFES", "IGUANAS", "SQUID", "RUST"]
}
场景二:处理集合数据
需求:给定一个用户列表,筛选出年龄大于 18 岁的用户,提取他们的用户名,然后按字母顺序排序。
rust
use itertools::Itertools;
#[derive(Debug)]
struct User {
username: String,
age: u8,
}
fn main() {
let users = vec![
User {
username: "alice".to_string(),
age: 20,
},
User {
username: "bob".to_string(),
age: 17,
},
User {
username: "charlie".to_string(),
age: 25,
},
User {
username: "dave".to_string(),
age: 19,
},
];
let adult_usernames: Vec<_> = users
.into_iter() // 消费型迭代,获取 User 所有权
.filter(|user| user.age > 18) // 筛选成年用户
.map(|user| user.username) // 提取用户名
.sorted() // 按字母排序(需导入 itertools 库)
.collect();
println!("成年用户:{:?}", adult_usernames); // ["alice", "charlie", "dave"]
}
场景三:并行迭代(Rayon 库)
对于大规模数据处理,Rust 可以通过 Rayon 库实现并行迭代,充分利用多核 CPU,提升处理效率。只需将普通迭代器替换为并行迭代器,无需修改核心逻辑。
rust
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
fn main() {
let numbers: Vec<u64> = (1..=1_000_000).collect();
// 并行求和,比普通迭代器更快(数据量越大,优势越明显)
let total: u64 = numbers.par_iter().sum();
println!("并行求和结果:{}", total); // 500000500000
}
迭代器性能优化
Rust 迭代器是零成本抽象的,编译后的代码与手写循环几乎完全一致(可通过 cargo asm --release 查看汇编代码验证),但如果使用不当,依然会出现性能问题。以下是常见的性能陷阱和优化技巧:
避免不必要的 collect()
多次调用 collect() 会导致中间集合的堆分配,增加内存开销。尽量将多个适配器链式调用,只调用一次 collect()。
rust
// ❌ 不好:多次 collect(),产生中间 Vec
fn bad_filter(data: &[i32]) -> Vec<i32> {
data.iter()
.filter(|&&x| x > 0)
.collect::<Vec<_>>() // 中间堆分配
.iter()
.map(|&x| x * 2)
.collect()
}
// ✅ 好:链式调用,只 collect() 一次
fn good_filter(data: &[i32]) -> Vec<i32> {
data.iter()
.filter(|&&x| x > 0)
.map(|&x| x * 2)
.collect() // 仅一次堆分配
}
利用 SIMD 自动向量化
对于连续内存的集合(如 Slice),迭代器的简单运算(如加法、乘法)会被编译器自动优化为 SIMD(单指令多数据)指令,大幅提升性能。触发 SIMD 优化的条件:
- 集合是连续内存(如
&[T],而非&Vec<T>); - 运算为可向量化操作(如
+、*、&等); - 无分支逻辑(避免
if条件判断)。
rust
// 向量加法的 SIMD 优化
fn simd_add(a: &[f32], b: &[f32]) -> Vec<f32> {
a.iter()
.zip(b.iter()) // 配对两个迭代器的元素
.map(|(&x, &y)| x + y)
.collect()
}
避免过度使用闭包捕获
闭包捕获外部变量会导致间接调用,影响编译器优化。如果闭包逻辑简单,可尝试使用直接函数或 fold 替代,提升性能。
rust
// ❌ 不好:闭包捕获 multiplier,间接调用
fn bad_map(data: &[i32], multiplier: i32) -> i32 {
data.iter().map(|&x| x * multiplier).sum()
}
// ✅ 好:使用 fold,更易被编译器优化
fn good_map(data: &[i32], multiplier: i32) -> i32 {
data.iter().fold(0, |acc, &x| acc + x * multiplier)
}
总结
最后,迭代器的核心是用更简洁的代码,做更高效、更安全的事。在日常开发中,尽量用迭代器替代手动循环,既能提升开发效率,也能让代码更具可读性和可维护性。