在这里插入图片描述

第 1 站:什么是“懒”?😴 (惰性求值 vs 及早求值)

想象一下,你妈妈让你做两件事:

  1. 打扫你的房间。
  2. 去商店买瓶酱油。
  • 及早求值 (Eager Evaluation):你立刻马上冲进房间,花了一个小时打扫得一尘不染… 然后你妈说:“哦,我们现在就要出门吃饭了,不用打扫了。” 😱 你的辛苦白费了!
  • 惰性求值 (Lazy Evaluation):你问妈妈:“你确定两件事都要做吗?” 妈妈说:“我先看看厨房… 啊,酱油没有了,你先去买酱油吧,房间不急。” 你只做了真正需要的“买酱油”这件事。

在编程中:

  • 及早求值 (Rust 的默认方式):只要你写了一行代码,比如 let x = 1 + 2;,Rust 会立即计算 1 + 2 并得到 3,然后把 3 绑定到 x
  • 惰性求值:一个表达式不会立即被计算,而是等到它真正被使用的时候才去计算。

Rust 并不是一门像 Haskell 那样的纯惰性语言,但它在特定领域,尤其是**迭代器(Iterators)**上,将惰性求值用到了极致!


第 2 站:见证魔法!Rust 惰性求值的核心 —— 迭代器 (Iterators) ✨

这是 Rust 中最最最常见的惰性求值场景,也是你作为初学者最需要掌握的!

1. 什么是迭代器?

迭代器是一种“能产生一系列值”的东西。比如一个数组 [1, 2, 3],它的迭代器就能依次产生 123

在 Rust 中,你通常通过 .iter() 方法来获取一个迭代器。

2. “懒”在哪里?—— 适配器 (Adapters)

迭代器有一堆很有用的方法,比如 .map() (对每个元素做点啥)、.filter() (过滤掉一些元素)等等。这些方法被称为迭代器适配器

重点来了:当你调用这些适配器时,它们什么也不做

它们不会立刻去遍历数据、不会去计算。它们只是返回一个新的迭代器结构体,这个结构体“数据、不会去计算。它们只是返回一个新的迭代器结构体,这个结构体“记住”了你想要做的操作。

举个栗子 🌰:

let numbers = vec![1, 2, 3, 4, 5];

// 我们调用 .map(),想让每个数字都乘以 2
// "lazy_map" 现在只是一个“计划”,它知道“将来要对 numbers 做 * 2 操作”
let lazy_map = numbers.iter().map(|x| {
    println!("正在处理: {}", x); // 👈 我们加个打印来看看
    x * 2
});

println!("--- 分割线:我们已经创建了 lazy_map ---");
// 注意!到目前为止,上面的 "正在处理: x" 一条都不会打印出来!
// 因为 .map() 是惰性的,根本没开始工作!
3. 什么时候“不懒”了?—— 消费者 (Consumers)

迭代器会一直“懒”下去,直到你调用一个消费者方法。

消费者方法是那些真正需要迭代器产出值的方法,比如:

  • .collect():把所有结果收集到一个新的集合(比如 Vec)里。
  • .sum():把所有结果加起来。
  • .for_each():对每个结果执行一个操作。
  • .next():手动获取下一个值。

当你调用消费者方法时,它就像一个工头喊:“开工了!” 👷

它会反过来“拉动” (pull) 迭代器链条,说:“collect 需要一个值!”,然后 lazy_map 就说:“好的,我需要一个值”,然后 iter() 就从 numbers 里取出 1map 把它变成 2collect 收集 2… 如此往复。

接上个栗子 🌰:

let numbers = vec![1, 2, 3, 4, 5];

let lazy_map = numbers.iter().map(|x| {
    println!("(map) 正在处理: {}", x);
    x * 2
});

println!("--- 分割线:我们已经创建了 lazy_map ---");

// 现在,我们调用 .collect() 来“消费”它
println!("--- 分割线:我们要开始 .collect() 了 ---");
let doubled_numbers: Vec<_> = lazy_map.collect();

println!("--- 分割线:.collect() 结束了 ---");
println!("结果: {:?}", doubled_numbers);

你会看到的输出:

--- 分割线:我们已经创建了 lazy_map ---
--- 分割线:我们要开始 .collect() 了 ---
(map) 正在处理: 1
(map) 正在处理: 2
(map) 正在处理: 3
(map) 正在处理: 4
(map) 正在处理: 5
--- 分割线:.collect() 结束了 ---
结果: [2, 4, 6, 8, 10]

看到了吗?map 里的 println! 是在 collect() 被调用时才一股脑执行的!

4. 惰性求值的好处:高效!

你可能会想:“这有啥好处?还不是一样要算?”

好处在于:你可以只做必要的工作。

想象一下,你有一个包含 1 亿个数字的列表,你想找到“第一个大于 100 并且是 3 的倍数的数字”。

  • **及早 (笨办法)**:

    1. 遍历 1 亿个数字,筛选出所有大于 100 的。(可能剩 8 千万个)
    2. 遍历这 8 千万个数字,筛选出 3 的倍数。(可能剩 2 千万个)
    3. 从这 2 千万个里,取出第一个。
      (你做了 1 亿 + 8 千万次次操作!)
  • 惰性求值 (Rust 迭代器)

let big_data = 0..1_000_000_000; // 0 到 10 亿,注意这是 Range,它本身也是惰性的!

let result = big_data
    .filter(|x| {
        // println!("Filter 1: {}", x); // 如果打开这个,你会看到它只跑到 102
        *x > 100
    })
    .filter(|x| {
        // println!("Filter 2: {}", x); // 如果打开这个,你会看到它只跑到 102
        *x % 3 == 0
    })
    .next(); // 👈 .next() 是消费者,它“只要一个”

match result {
    Some(val) => println!("找到了: {}", val), // 会打印 "找到了: 102"
    None => println!("没找到"),
}

Rust 的迭代器链条是这样工作的:

  1. .next() 说:“给我一个值!”
  2. 第二个 .filter 说:“行,我需要一个值。”
  3. 第一个 .filter 说:“行,我需要一个值。”
  4. big_data 给出 0… (不满足 > 100)
  5. big_data 给出 101… (满足 > 100,但不满足 % 3 == 0)
  6. big_data 给出 102… (满足 > 100,也满足 % 3 == 0)
  7. 102 被传递给 .next()
  8. .next() :“我拿到值了,收工!”

整个过程只计算到了 102 就停止了!后面的 9 亿 9 千 9 百… 万个数字,根本碰都没碰!这就是惰性求值的巨大优势!👍


第 3 站:其他“懒”家伙们 ⚡️

虽然迭代器是主角,但 Rust 中还有其他几种惰性求值的体现:

1. 短路求值 (Short-circuiting)

你肯定见过的 && (与) 和 || (或)。

  • `a && b:如果 afalse,Rust 根本不会去计算 b 是什么。
  • a || b:如果 `true,Rust *根本不会*去计算 b` 是什么。
fn expensive_call() -> bool {
    println!("一个非常昂贵的计算!");
    true
}

// 因为 false && ... 必然是 false,所以右边的函数根本不会被调用
if false && expensive_call() {
    // ...
}

// 因为 true || ... 必然是 true,所以右边的函数也根本不会被调用
if true || expensive_call() {
    // ...
}
2. 闭包 (Closures)

闭包(|| { ... } 这种匿名函数)本身也是惰性的。你定义一个闭包时,它里面的代码并不会运行。

let lazy_print = || {
    println!("我被调用啦!");
};

println!("闭包已定义,但未调用");
// 只有在这里真正“调用”它时,它才执行
lazy_print();

这在 OptionResult 的组合子方法中特别有用:

  • opt.or(Some(expensive_call()))及早求值expensive_call() 总是会执行,不管 optSome 还是 None
  • `opt.or_else(|| Some(expensive_call):**惰性求值**。expensive_call()*只在*optNone` 的时候才会被调用。
3. 异步 async/await 🚀

(这个稍微进阶一点,你先有个印象)

在 Rust 的异步编程中,当你调用一个 async fn 时,它会返回一个 Future (未来)。

这个 Future 也是惰性的!它就是一个“待办事项”,描述了“将来要干什么”。它自己什么也不做,直到你把它交给一个“执行器”(Executor) 并且 `await` 它,它才会真正开始运行。


总结:你的 Rust 懒人包 🧠

恭喜你完成了新手教程!🎉

让我们来总结一下:

  1. Rust 默认是“及早求值” (Eager) 的,写了就算。
  2. Rust 的“惰性求值” (Lazy) 主要体现在迭代器上。
  3. 迭代器适配器(如 .map, .filter)是惰性的。它们只构建一个“计划”(返回一个新的迭代器),不干活。
    4*迭代器消费者**(如 .collect, .sum, .next)是主动的。它们“拉动”迭代器链条,触发所有计算。
  4. 好处:极高效率,只做必要的工作,特别适合处理大数据流或无限序列。
    6*其他惰性形式**:&& / || 短路、or_else 这样的闭包组合子、`asyncawaitFuture`。

你已经掌握了 Rust 中一个非常强大且核心的概念!多加练习,尝试用 .map().filter() 组合一些操作,然后用 .collect() 把它们“变”出来,你很快就能熟练掌握啦!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐