bigywater.tech
Rust · 62

Iterator 的惰性执行

「迭代器是惰性的」这句话该怎么验证?这篇用带 println 的闭包、计数器和 size_of,把「不消费不执行」「逐个元素穿过整条链」「短路后源不再产出」「链只是一个小结构体」都直接测出来。

0 次
只调用 map 不消费时闭包的执行次数
9 个
filter(%3==0).take(3) 从 100 万个元素的源里实际拉取的个数
16 字节
在 100 万元素 Vec 上的 map+filter 链的 size_of

不消费就不执行

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let v = vec![1, 2, 3];
      3 |     v.iter().map(|x| { println!("闭包执行了 x={}", x); x * 2 });
      4 |     println!("上一行之后,没有任何「闭包执行了」输出");
      5 | }
  编译:成功;警告:
    warning: unused `Map` that must be used
     --> <实验目录>/src/u62a.rs:3:5
      |
    3 |     v.iter().map(|x| { println!("闭包执行了 x={}", x); x * 2 });
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: iterators are lazy and do nothing unless consumed
      = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
    help: use `let _ = ...` to ignore the resulting value
      |
    3 |     let _ = v.iter().map(|x| { println!("闭包执行了 x={}", x); x * 2 });
      |     +++++++
    
    warning: 1 warning emitted
  运行(退出码 0):
    上一行之后,没有任何「闭包执行了」输出

逐个元素穿过整条链

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let r: Vec<i32> = (1..=4)
      3 |         .inspect(|x| println!("  源      产出 {}", x))
      4 |         .filter(|x| { let keep = x % 2 == 0; println!("  filter  {} -> {}", x, keep); keep })
      5 |         .map(|x| { println!("  map     {} -> {}", x, x * 10); x * 10 })
      6 |         .collect();
      7 |     println!("结果 {:?}", r);
      8 | }
  编译:成功
  运行(退出码 0):
      源      产出 1
      filter  1 -> false
      源      产出 2
      filter  2 -> true
      map     2 -> 20
      源      产出 3
      filter  3 -> false
      源      产出 4
      filter  4 -> true
      map     4 -> 40
    结果 [20, 40]

Iterator 的基本协议见Iterator Trait。

短路

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    filter(%3==0).take(3) = [3, 6, 9]  源总共产出 9 个
    find(==7) = Some(7)  源总共产出 7 个
    any(>100) = true  源总共产出 101 个
    take(5).count() = 5  源总共产出 5 个
    take_while(<4).sum() = 6  源总共产出 4 个(多产出一个用来发现 4 不满足条件)

链就是嵌套的结构体

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    无穷序列取前 5 个偶数的平方: [4, 16, 36, 64, 100]
    类型: map::Map<filter::Filter<Range<i32>, u62d::main::{{closure}}>, u62d::main::{{closure}}>
    size_of(这条链) = 12 字节(Range<i32> 本身 8 字节;闭包 filter 无捕获、map 捕获了 k)
    在 100 万元素 Vec 上建一条 map+filter 链: size_of = 16 字节(只存了迭代器状态,不存元素)

自定义适配器见自定义 Iterator。

惰性的代价

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let it;
      3 |     {
      4 |         let v = vec![1, 2, 3];
      5 |         it = v.iter().map(|x| x * 2);
      6 |     }
      7 |     println!("{:?}", it.collect::<Vec<_>>());
      8 | }
  编译:失败
    error[E0597]: `v` does not live long enough
     --> <实验目录>/src/u62e.rs:5:14
      |
    4 |         let v = vec![1, 2, 3];
      |             - binding `v` declared here
    5 |         it = v.iter().map(|x| x * 2);
      |              ^ borrowed value does not live long enough
    6 |     }
      |     - `v` dropped here while still borrowed
    7 |     println!("{:?}", it.collect::<Vec<_>>());
      |                      -- borrow later used here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0597`.
  编译:成功
  运行(退出码 0):
    建好链之后 log = (此时不能读,因为闭包还借着 log)
    消费之后 log = [1, 2, 3]

分步演示

Iterator 惰性
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:并行迭代器(rayon)的惰性、DoubleEndedIterator 的 rev 与惰性、链在 release 下被内联后的汇编(性能放在「Iterator 链的性能」一篇)、try_fold 与短路的细节
    京ICP备2026067502号-1京公网安备11010502063487号