bigywater.tech
Rust · 63

map、filter、fold

这三个最常用的迭代器适配器,细节一点也不少:闭包拿到的是引用还是值?fold 的顺序有什么影响?出错能不能提前停?链写出来的机器码和手写循环一样吗?这篇都用真实编译与运行来回答。

&&i32
v.iter().filter 的闭包参数类型
48 对 24
[10,3,2] 上 fold 与 rfold 同一个闭包的结果
60 对 65
fold 与 for 循环的汇编指令条数(都向量化)

类型与引用

实测 · 本机执行输出
  源码:
      1 | fn tn<T>(_: &T) -> &'static str { std::any::type_name::<T>() }
      2 | fn main() {
      3 |     let v = vec![1, 2, 3, 4, 5, 6];
      4 |     let a: Vec<String> = v.iter().map(|x| format!("#{}", x)).collect();
      5 |     println!("map i32 -> String: {:?}", a);
      6 |     let b: Vec<&i32> = v.iter().filter(|x| { println!("  filter 闭包的参数类型 {}", tn(&x)); **x % 2 == 0 }).take(1).collect();
      7 |     println!("filter 之后元素类型仍是 &i32: {:?}", b);
      8 |     let c: Vec<i32> = v.iter().copied().filter(|x| x % 2 == 0).collect();
      9 |     println!("copied 之后 filter 参数是 &i32,可以直接 x % 2: {:?}", c);
     10 | }
  编译:成功
  运行(退出码 0):
    map i32 -> String: ["#1", "#2", "#3", "#4", "#5", "#6"]
      filter 闭包的参数类型 &&i32
      filter 闭包的参数类型 &&i32
    filter 之后元素类型仍是 &i32: [2]
    copied 之后 filter 参数是 &i32,可以直接 x % 2: [2, 4, 6]
  源码:
      1 | fn main() {
      2 |     let v = vec![1, 2, 3];
      3 |     let c: Vec<&i32> = v.iter().filter(|x| x % 2 == 0).collect();
      4 |     println!("{:?}", c);
      5 | }
  编译:失败
    error[E0369]: cannot calculate the remainder of `&&{integer}` divided by `{integer}`
     --> <实验目录>/src/u63b.rs:3:46
      |
    3 |     let c: Vec<&i32> = v.iter().filter(|x| x % 2 == 0).collect();
      |                                            - ^ - {integer}
      |                                            |
      |                                            &&{integer}
      |
    help: `%` can be used on `&{integer}` if you dereference the left-hand side
      |
    3 |     let c: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();
      |                                            +
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0369`.

惰性的具体含义见Iterator 的惰性执行。

filter_map 与 collect

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    filter_map(parse.ok): [10, 30, 50]
    map(parse).flatten():  [10, 30, 50]
    collect 成 Result:     Err(ParseIntError { kind: InvalidDigit })

fold 与 reduce

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    fold(0, a*2+x) 从左到右: 48   rfold 从右到左: 24
    fold 的累加器是 String(元素是 i32): "10-3-2"
    fold 同时算 (和, 个数): (15, 3)
    reduce(max): Some(10)   空序列 reduce: None   空序列 fold(0,..): 0
    scan 保留中间状态: [10, 13, 15]

try_fold

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    u8 的 checked_add:total None(看了 4 个元素,累加到第 4 个溢出)
    不溢出的情形: Some(6)

汇编对比

实测 · 本机执行输出
  汇编里的符号别名: [('_sum_iter', '_sum_fold')]
  sum_for          指令 65 条  向量指令(.4s/.2d)条数 15
  sum_fold         指令 60 条  向量指令(.4s/.2d)条数 15
  sum_iter         指令 0 条  向量指令(.4s/.2d)条数 0
  sum_map_filter   指令 95 条  向量指令(.4s/.2d)条数 39
  sum_manual       指令 100 条  向量指令(.4s/.2d)条数 39
  sum_for 与 sum_fold(规范化标签后)逐行相同: False;  map+filter+fold 与手写循环逐行相同: False

指令条数来自 aarch64 的 -O 汇编,换平台数字会不同。

分步演示

map / filter / fold
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。汇编用 rustc --emit=asm -O 生成,并规范化局部标签后比较
    • 没有验证:x86_64 平台的汇编、浮点数 fold(向量化会因结合律受限)、sum()/product() 的内部实现、大数据量下三种写法的运行时间(只比较了汇编)
    京ICP备2026067502号-1京公网安备11010502063487号