当前位置: 首页 > news >正文

趣味学RUST基础篇(函数式编程迭代器)

Rust 迭代器:一个“懒人”如何高效打工

想象一下,你是一个程序员,刚入职一家叫 “Rust公司” 的高科技企业。你的任务是处理一堆数据,比如:一个装着数字 [1, 2, 3] 的盒子。

第一幕:创建一个“打工人”——迭代器

你不能直接把盒子拆开一个个数,太原始了!Rust 公司给你配了一个聪明的“打工人”——迭代器(Iterator)

let v1 = vec![1, 2, 3];
let v1_iter = v1.iter(); // 召唤“打工人”

这就像你对 HR 说:“嘿,给我派个实习生,让他帮我数数这个盒子里有啥。”

重点来了:这个实习生超级“懒”!
你刚叫他,他只是站在那儿,啥也不干。这就是 “惰性(lazy)” ——不干活,除非你下命令。

第二幕:让他干活——for 循环

你终于发话了:“开始干活,把每个数都打印出来!”

for val in v1_iter {println!("Got: {val}");
}

实习生立刻动起来:

  • 第一天:拿出 1,打印。
  • 第二天:拿出 2,打印。
  • 第三天:拿出 3,打印。
  • 第四天:盒子空了,他交出 None,下班!

内部发生了啥?
实习生其实有个 next() 方法,每次调用就吐出一个值:

    #[test]fn iterator_demonstration() {let v1 = vec![1, 2, 3];let mut v1_iter = v1.iter();assert_eq!(v1_iter.next(), Some(&1));assert_eq!(v1_iter.next(), Some(&2));assert_eq!(v1_iter.next(), Some(&3));assert_eq!(v1_iter.next(), None);}

小贴士:要让实习生动起来,得把他变成“可变”的(mut),因为他得记住自己数到哪儿了。


第三幕:实习生也能“算工资”——消费适配器

你又说:“别光打印了,把所有数加起来,算个总和!”

    #[test]fn iterator_sum() {let v1 = vec![1, 2, 3];let v1_iter = v1.iter();let total: i32 = v1_iter.sum();assert_eq!(total, 6);}

实习生立刻把盒子拿过来,从头到尾数一遍,累加,最后交出 6
但注意!他把盒子拿走后就不还你了!
这就是 “消费适配器(consuming adaptor) ——用完就“吃掉”迭代器,你不能再用它了。


第四幕:实习生变身“改造大师”——迭代器适配器

现在你不想让他算总数,而是想让每个数都 加 1。你对他说:

v1.iter().map(|x| x + 1);

实习生听完,还是站着不动!
为啥?因为他又“懒”了。他只是说:“哦,我知道了,如果让我干,我就把每个数加1。”
但你不让他干活,他就不动。

警告:Rust 编译器会提醒你:“你造了个改造大师,但没用他,浪费了!”

怎么让他干活?得让他“变现”!比如,让他把改造后的结果 收集 起来:

//main.rs
let v1: Vec<i32> = vec![1, 2, 3];let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);

collect() 就像说:“把你的成果打包成一个新盒子交给我!”
这叫 “迭代器适配器(iterator adaptor) ——它不消耗原数据,而是生成一个新“打工人”来做改造。


第五幕:实习生还会“筛选”——filter

你有一堆鞋子,想找出所有 10码 的。

#[derive(PartialEq, Debug)]
struct Shoe {size: u32,style: String,
}fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}#[cfg(test)]
mod tests {use super::*;#[test]fn filters_by_size() {let shoes = vec![Shoe {size: 10,style: String::from("sneaker"),},Shoe {size: 13,style: String::from("sandal"),},Shoe {size: 10,style: String::from("boot"),},];let in_my_size = shoes_in_size(shoes, 10);assert_eq!(in_my_size,vec![Shoe {size: 10,style: String::from("sneaker")},Shoe {size: 10,style: String::from("boot")},]);}
}

你对实习生说:

  1. “把这堆鞋子拿走(into_iter)。”
  2. “只留下鞋码是 shoe_size 的(filter)。”
  3. “把留下的鞋子装个新盒子给我(collect)。”

这里的 filter 用了一个 闭包(closure),它“偷看”了你定义的 shoe_size 变量,就像实习生记住了你的指令。


迭代器的“三大纪律”

  1. 它是懒的:不调用 forsumcollect 这些“消费”方法,它绝不干活。
  2. 它分两种
    • 消费适配器:像 sum,用完就“吃掉”迭代器。
    • 迭代器适配器:像 mapfilter,它们“改造”迭代器,但不消费,需要你再用 collect 来“变现”。
  3. 它很灵活:你可以把多个适配器串起来

Rust 进化论:从“搬砖”到“念咒”

还记得我们之前写的那个“搜索文件”小工具吗?它能帮你在一个文本文件里找关键词,比如:“哪里提到了‘猫’?”但当时的代码,就像一个原始人用石头和木棍打猎——能用,但不够优雅。

今天,我们要用 “迭代器魔法” 给它来一次大升级,让它从“搬砖”变成“念咒”!


阶段一:原始人搬砖 —— clonefor 循环

在旧版本里,我们的 Config::new 函数是这样工作的:

let query = args[1].clone();
let file_path = args[2].clone();

这就像你对助手说:

“去,把第一个参数拿过来,复印一份给我;再把第二个参数拿过来,也复印一份!”

为什么要“复印”?因为助手(函数)不能直接拿走你的东西,他得自己留一份。

问题来了:复印(clone)很浪费时间!我们能不能直接把原件给他?


阶段二:念咒语 —— 用迭代器“隔空取物”

好消息!Rust 的 env::args() 函数其实返回的不是一个“参数列表”,而是一个 “参数生成器” ——也就是迭代器

它就像一个会自动吐出参数的魔法盒子,你只要说“下一个!”,它就吐一个。

第一步:把“复印机”扔了,直接传“魔法盒子”

我们不再把参数收集到 Vec 里再传 slice,而是直接把“魔法盒子”(迭代器)交给 Config::new:

// 旧的:先收集,再传 slice
let args: Vec<String> = env::args().collect();
let config = Config::new(&args)...;// 新的:直接传“魔法盒子”!
let config = Config::new(env::args())...;

看,多干净!连 collect() 都省了。

第二步:改写 new 函数,学会“念咒”

现在 new 函数要改了,它不再接受一个“复印好的列表”,而是接受一个“会吐参数的盒子”:

impl Config {pub fn new(mut args: impl Iterator<Item = String>, // 接收任何能吐出 String 的“盒子”) -> Result<Config, &'static str> {args.next(); // 第一个是程序名,扔掉!(念:下一个!)let query = match args.next() {Some(arg) => arg, // 拿到第二个,就是 queryNone => return Err("没给关键词!"),};let file_path = match args.next() {Some(arg) => arg, // 拿到第三个,就是文件路径None => return Err("没给文件路径!"),};// 其他逻辑不变...Ok(Config { query, file_path, ignore_case })}
}

关键点

  • 我们用 args.next() 来“念咒”,让盒子吐出下一个参数。
  • 因为是直接“拿走”参数,所以 不需要 clone!省时省力。
  • 如果盒子空了(None),就说明参数不够,报错!

进化成功!从“复印”到“隔空取物”,代码更高效了!


🔍 阶段三:搜索函数也来“念咒”!

再看搜索函数 search,旧版本是这样:

pub fn search(query: &str, contents: &str) -> Vec<&str> {let mut results = Vec::new(); // 准备一个空篮子for line in contents.lines() { // 一行行看if line.contains(query) {  // 如果这行有关键词results.push(line);    // 放进篮子}}results // 返回篮子
}

这就像一个工人,一行一行地检查,符合条件就放进篮子里。虽然能干,但太“机械”了。

用迭代器“念咒”:
pub fn search(query: &str, contents: &str) -> Vec<&str> {contents.lines()                    // 把文本拆成“行流”.filter(|line| line.contains(query)) // 念咒:只留下包含关键词的行.collect()                  // 把结果“打包”成 vector
}

一句话搞定!就像对魔法阵下令:

“把所有行过滤一遍,只留下包含‘猫’的,然后打包给我!”

优点

  • 代码更短:从 7 行变成 4 行。
  • 意图更清晰:一眼看出“过滤 + 收集”的逻辑。
  • 没有可变变量results 篮子没了,代码更“函数式”,更安全。
  • 性能可能更好:Rust 编译器对迭代器优化得非常好。

循环 vs 迭代器:选哪个?

你可能会问:“for 循环”和“迭代器”哪个更好?

风格优点缺点适合谁
for 循环直观,像“手把手教”代码长,容易出错初学者
迭代器简洁,意图明确,更安全初学时有点抽象Rust 老手

Rust 社区的共识优先使用迭代器

因为它:

  1. 更少的可变状态 → 更少的 bug。
  2. 更易并行化 → 未来可以轻松改成多线程搜索。
  3. 更接近“做什么”,而不是“怎么做” → 代码更易维护。

从“搬砖工”到“魔法师”

阶段工具代码风格比喻
原始人clone, for搬砖、复印用石头打猎
未来战士迭代器念咒、魔法用激光枪

记住这三句咒语

  1. args.next() —— “下一个!”
  2. .filter(|x| ...) —— “只留下符合条件的!”
  3. .collect() —— “打包带走!”

从此,你不再是那个手动 for i in 0..len 的“搬砖工”,而是一个能用一行代码搞定复杂逻辑的 Rust 魔法师

Rust 性能大对决:手动挡 vs 自动挡,谁更快?

我们可以把循环和迭代器比作手动挡和自动挡,想象一下,你正在看一场赛车比赛。

赛道左边是一辆纯手动挡赛车——它代表我们手写的 for 循环。
赛道右边是一辆智能自动挡赛车——它代表我们用迭代器写的代码。

它们的任务是:在一本厚厚的《福尔摩斯探案集》里,找出所有“the”这个单词,看谁完成得更快!

第一回合:正式比赛开始!

我们把书加载进内存,两辆车同时发车!

成绩出来了!

  • 手动挡(for 循环):19,620,300 纳秒(约 0.02 秒)
  • 自动挡(迭代器):19,234,900 纳秒(约 0.019 秒)

什么?!自动挡居然还快了一丢丢?!

这就像你本以为手动换挡能更精准控制,结果发现自动变速箱的 AI 换挡比你还快还顺!

结论迭代器 ≠ 慢!它和手写循环一样快,甚至更快!


为什么自动挡这么猛?

因为 Rust 的编译器,是个超级赛车调校大师

它看到你写的“高级”代码,比如:

.filter(|line| line.contains(query))
.collect()

它不会傻乎乎地照着代码一行行翻译成机器指令。
相反,它会说:“哦,用户想过滤再收集?我懂了,我给你优化成最高效的汇编代码!”

这就像:

  • 你对导航说:“带我去最近的咖啡馆。”
  • 导航不会真的“找最近”,而是直接算出最优路线,带你飞过去。

Rust 编译器就是这么聪明!


第二回合:更复杂的赛道 —— 音频解码器

这次的赛道更难了!我们要解码一段音频,计算一个叫 prediction 的值。

手动挡车手会怎么写?

for i in 12..buffer.len() {let mut sum = 0;for j in 0..12 {sum += coefficients[j] * buffer[i - 12 + j] as i64;}let prediction = sum >> qlp_shift;// ... 后续操作
}

嵌套循环,手动索引,容易出错,看得人头大。

自动挡车手(Rust 迭代器) 怎么写?

let prediction = coefficients.iter().zip(&buffer[i - 12..i]).map(|(&c, &s)| c * s as i64).sum::<i64>() >> qlp_shift;

一句话,清晰明了:“把系数和数据配对,相乘,求和,再右移。”

结果呢?

在编写这本书的时候,这两段代码被编译成了完全相同的汇编代码

编译器看到 coefficients.iter() 只有 12 个元素,直接把循环展开(unroll)——也就是把 12 次循环变成 12 行重复代码,彻底干掉了“循环计数”的开销!

所有数据都放进寄存器(CPU 的超级高速缓存),访问飞快!
没有数组越界检查!没有多余的跳转!

这就是 Rust 的“零成本抽象”(Zero-Cost Abstractions)!


什么是“零成本抽象”?

简单说就是:

你用高级语法写的代码,Rust 编译器能把它变成和你手写汇编一样高效的机器码。

就像:

  • 你用“自动驾驶”模式开车,结果发现它比你手动开还省油、还安全。
  • 你用“美颜滤镜”拍照,结果发现画质和原图一模一样,只是更好看了。

Rust 说:

“你不用的,不收你钱(不引入开销);你用了的,我给你做到极致(不可能手写更好)。”

这正是 C++ 之父 Bjarne Stroustrup 说的 “零开销(zero-overhead)” 原则!


放心大胆用迭代器!

项目手写 for 循环Rust 迭代器
性能一样快,甚至更快
可读性难懂,易错清晰,意图明确
安全性容易越界编译器帮你检查
维护性难改易重构

所以,别再担心“用迭代器会不会变慢”了!

Rust 的编译器是你的超级外挂。你只管用优雅、安全的高级语法写代码,剩下的性能优化,交给它!

现在,放下对性能的担忧,大胆地使用闭包和迭代器,写出既漂亮又飞快的 Rust 代码吧!


文章转载自:

http://SkWvXFp5.Lzwfg.cn
http://f1hfjvlg.Lzwfg.cn
http://sWyZvc9R.Lzwfg.cn
http://5k81tL8g.Lzwfg.cn
http://WdcDYfbD.Lzwfg.cn
http://pznlrLfh.Lzwfg.cn
http://sNvqc7sZ.Lzwfg.cn
http://cMBTdHRL.Lzwfg.cn
http://G7ZUsq1r.Lzwfg.cn
http://S4esRzul.Lzwfg.cn
http://xaXDboxv.Lzwfg.cn
http://YIyKAYRR.Lzwfg.cn
http://2x8uodGo.Lzwfg.cn
http://ixJJwKnY.Lzwfg.cn
http://3pX1gQQ4.Lzwfg.cn
http://28kJLQts.Lzwfg.cn
http://3XE8HD4S.Lzwfg.cn
http://cIuREpzn.Lzwfg.cn
http://ujZmPpky.Lzwfg.cn
http://LvGesIZO.Lzwfg.cn
http://7i2i6vkx.Lzwfg.cn
http://D4v2lx0m.Lzwfg.cn
http://ny8kOC1Z.Lzwfg.cn
http://6OYbMuNK.Lzwfg.cn
http://bGIN33kB.Lzwfg.cn
http://8Y1YsC42.Lzwfg.cn
http://IdVyd8vC.Lzwfg.cn
http://i0xS4wsZ.Lzwfg.cn
http://mQIYr3DA.Lzwfg.cn
http://fOayTZfW.Lzwfg.cn
http://www.dtcms.com/a/375217.html

相关文章:

  • 抗ASIC、抗GPU 的密码哈希算法(安全密钥派生)Argon2算法
  • Nginx 实战系列(六)—— Nginx 性能优化与防盗链配置指南
  • 深入解析 Apache Flink Checkpoint 与 Savepoint 原理与最佳实践
  • C#WPF控制USB摄像头参数:曝光、白平衡等高级设置完全指南
  • 第2节-过滤表中的行-IN
  • 2025年渗透测试面试题总结-60(题目+回答)
  • 【GD32】ROM Bootloader、自定义Bootloader区别
  • 业务用例和系统用例
  • Google AI Mode 颠覆传统搜索方式,它是有很大可能的
  • MTC出席SAP大消费峰会:行业深度×全球广度×AI创新,助力韧性增长
  • 彩笔运维勇闯机器学习--决策树
  • 成都金牛区哪里租好办公室?国际数字影像产业园享税收优惠
  • vue3 实现将页面生成 pdf 导出(html2Canvas + jspdf)
  • golang 面试常考题
  • 单例模式(C++)
  • All in AI之二:数学体系的建立
  • 【Python】S1 基础篇 P5 字典模块指南
  • MySQL底层架构设计原理详细介绍
  • 《ServiceMesh落地避坑指南:从智慧园区故障看Envoy配置治理》
  • 【ARMv7-M】复位向量与启动过程
  • SQL面试题及详细答案150道(136-150) --- 性能优化与数据库设计篇
  • CMake Qt程序打包与添加图标详细教程
  • 【MySQL】mysql-connector-cpp使用
  • Oracle RAC认证矩阵:规避风险的关键指南
  • CTF-Web手的百宝箱
  • Django高效查询:values_list实战详解
  • Redis核心数据结构
  • 海外代理IP平台Top3评测:LoongProxy、神龙动态IP、IPIPGO哪家更适合你?
  • 开发避坑指南(43):idea2025.1.3版本启动springboot服务输入jvm参数解决办法
  • Vue3入门到实战,最新版vue3+TypeScript前端开发教程,笔记03