深入浅出 Rust 迭代器:从基础用法到性能优化

深入浅出 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()mapfilter 的结合体,闭包返回 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(|&amp;&amp;x| x &gt; 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)
}

总结

最后,迭代器的核心是用更简洁的代码,做更高效、更安全的事。在日常开发中,尽量用迭代器替代手动循环,既能提升开发效率,也能让代码更具可读性和可维护性。

相关推荐
@atweiwei3 小时前
langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)
开发语言·rust·langchain·agent·向量数据库·rag
skilllite作者5 小时前
自进化 Agent 的 skills 别长成烟囱:从多入口分叉到统一发现与 spec 防火带
人工智能·算法·rust·openclaw·agentskills
Rust研习社13 小时前
关于 Rust Option 的那些事:从基础到常用 API 全解析
rust
爱分享的阿Q17 小时前
Rust加WebAssembly前端性能革命实践指南
前端·rust·wasm
沉淀粉条形变量1 天前
rust 单例模式
开发语言·单例模式·rust
skilllite作者1 天前
SkillLite 多入口架构实战:CLI / Python SDK / MCP / Desktop / Swarm 一页理清
开发语言·人工智能·python·安全·架构·rust·agentskills
Rust研习社1 天前
深入理解 Rust 闭包:从基础语法到实战应用
rust
Rust研习社1 天前
Rust 时间处理神器:chrono 从入门到实战
rust
Rust研习社1 天前
Rust 异步 ORM 新选择:Toasty 初探
rust