bigywater.tech
Rust · 53

自定义 Iterator

自己写迭代器很简单,但要写得好需要几个小知识:结构体里放什么、怎样借用外部数据、要不要实现双端与精确长度、size_hint 的作用。这篇用四个例子逐条验证。

92 项
自定义斐波那契迭代器在 u64 溢出前的项数
E0277
没实现 DoubleEndedIterator 就调用 rev()
1024 → 1000
collect 的 capacity:没有与有 size_hint

一个结构体加一个 next()

实测 · 本机执行输出
  源码:
      1 | struct Fib { a: u64, b: u64 }
      2 | impl Fib { fn new() -> Self { Fib { a: 0, b: 1 } } }
      3 | impl Iterator for Fib {
      4 |     type Item = u64;
      5 |     fn next(&mut self) -> Option<u64> {
      6 |         let r = self.a;
      7 |         let next = self.a.checked_add(self.b)?;        // 溢出时返回 None,迭代结束
      8 |         self.a = self.b; self.b = next; Some(r)
      9 |     }
     10 | }
     11 | fn main() {
     12 |     println!("前 10 项:{:?}", Fib::new().take(10).collect::<Vec<_>>());
     13 |     println!("小于 100 的偶数项:{:?}", Fib::new().take_while(|&x| x < 100).filter(|x| x % 2 == 0).collect::<Vec<_>>());
     14 |     println!("一共有 {} 项(在 u64 溢出之前),最后一项 {:?}", Fib::new().count(), Fib::new().last());
     15 | }
  编译:成功
  运行(退出码 0):
    前 10 项:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    小于 100 的偶数项:[0, 2, 8, 34]
    一共有 92 项(在 u64 溢出之前),最后一项 Some(4660046610375530309)

Iterator 的基本约定见Iterator Trait。

借用数据

实测 · 本机执行输出
  源码:
      1 | struct Pairs<'a, T> { items: &'a [T], i: usize }
      2 | impl<'a, T> Iterator for Pairs<'a, T> {
      3 |     type Item = (&'a T, &'a T);
      4 |     fn next(&mut self) -> Option<Self::Item> {
      5 |         if self.i + 1 >= self.items.len() { return None; }
      6 |         let p = (&self.items[self.i], &self.items[self.i + 1]); self.i += 1; Some(p)
      7 |     }
      8 | }
      9 | fn pairs<T>(items: &[T]) -> Pairs<'_, T> { Pairs { items, i: 0 } }
     10 | fn main() {
     11 |     let words = vec!["a".to_string(), "b".to_string(), "c".to_string()];
     12 |     for (x, y) in pairs(&words) { println!("{x}-{y}"); }
     13 |     println!("差分:{:?}", pairs(&[1, 4, 9, 16]).map(|(a, b)| b - a).collect::<Vec<_>>());
     14 | }
  编译:成功
  运行(退出码 0):
    a-b
    b-c
    差分:[3, 5, 7]

双端与精确长度

实测 · 本机执行输出
  编译:失败
    error[E0277]: the trait bound `Count: DoubleEndedIterator` is not satisfied
        --> <实验目录>/src/u53c.rs:6:53
         |
       6 | fn main() { println!("{:?}", Count { lo: 0, hi: 3 }.rev().collect::<Vec<_>>()); }
         |                                                     ^^^ unsatisfied trait bound
         |
    help: the trait `DoubleEndedIterator` is not implemented for `Count`
        --> <实验目录>/src/u53c.rs:1:1
         |
       1 | struct Count { lo: u32, hi: u32 }
         | ^^^^^^^^^^^^
    note: required by a bound in `rev`
        --> <rust 标准库源码>/library/core/src/iter/traits/iterator.rs:3445:23
         |
    3443 |     fn rev(self) -> Rev<Self>
         |        --- required by a bound in this associated function
    3444 |     where
    3445 |         Self: Sized + DoubleEndedIterator,
         |                       ^^^^^^^^^^^^^^^^^^^ required by this bound in `Iterator::rev`
    
    error[E0599]: the method `collect` exists for struct `Rev<Count>`, but its trait bounds were not satisfied
      --> <实验目录>/src/u53c.rs:6:59
       |
     1 | struct Count { lo: u32, hi: u32 }
    …(共 49 行)
  源码:
      1 | struct Count { lo: u32, hi: u32 }
      2 | impl Iterator for Count {
      3 |     type Item = u32;
      4 |     fn next(&mut self) -> Option<u32> { if self.lo >= self.hi { None } else { self.lo += 1; Some(self.lo - 1) } }
      5 |     fn size_hint(&self) -> (usize, Option<usize>) { let n = (self.hi - self.lo) as usize; (n, Some(n)) }
      6 | }
      7 | impl DoubleEndedIterator for Count {
      8 |     fn next_back(&mut self) -> Option<u32> { if self.lo >= self.hi { None } else { self.hi -= 1; Some(self.hi) } }
      9 | }
     10 | impl ExactSizeIterator for Count {}
     11 | fn main() {
     12 |     println!("rev:{:?}", Count { lo: 0, hi: 5 }.rev().collect::<Vec<_>>());
     13 |     println!("len:{},size_hint:{:?}", Count { lo: 2, hi: 9 }.len(), Count { lo: 2, hi: 9 }.size_hint());
     14 |     let mut c = Count { lo: 0, hi: 6 };
     15 |     println!("两头同时取:{:?} {:?} {:?} {:?}", c.next(), c.next_back(), c.next(), c.next_back());
     16 | }
  编译:成功
  运行(退出码 0):
    rev:[4, 3, 2, 1, 0]
    len:7,size_hint:(7, Some(7))
    两头同时取:Some(0) Some(5) Some(1) Some(4)

size_hint 的价值

实测 · 本机执行输出
  源码:
      1 | struct NoHint(u32); struct WithHint(u32);
      2 | impl Iterator for NoHint { type Item = u32; fn next(&mut self) -> Option<u32> { if self.0 == 0 { None } else { self.0 -= 1; Some(self.0) } } }
      3 | impl Iterator for WithHint { type Item = u32; fn next(&mut self) -> Option<u32> { if self.0 == 0 { None } else { self.0 -= 1; Some(self.0) } } fn size_hint(&self) -> (usize, Option<usize>) { (self.0 as usize, Some(self.0 as usize)) } }
      4 | fn main() {
      5 |     let a: Vec<u32> = NoHint(1000).collect(); let b: Vec<u32> = WithHint(1000).collect();
      6 |     println!("没有 size_hint:collect 之后 len = {},capacity = {}", a.len(), a.capacity());
      7 |     println!("有 size_hint  :collect 之后 len = {},capacity = {}", b.len(), b.capacity());
      8 | }
  编译:成功
  运行(退出码 0):
    没有 size_hint:collect 之后 len = 1000,capacity = 1024
    有 size_hint  :collect 之后 len = 1000,capacity = 1000

让自己的集合也能被 for 遍历,见IntoIterator 的三种遍历方式。

分步演示

自定义 Iterator
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:迭代器里使用 impl Trait 返回类型的另一种写法、对 size_hint 实现错误(上下界不一致)的后果、FusedIterator 的实现、Iterator::try_fold 的重写
    京ICP备2026067502号-1京公网安备11010502063487号