如何表达流式 trait?

解读

“流式 trait”在国内 Rust 面试语境里通常指 Iterator 及其配套 Iterator adaptor 的链式调用风格:
iter().map(..).filter(..).take(..).collect()
面试官想确认两点:

  1. 你是否真正理解 Iterator trait 的核心定义next 方法 + 关联类型 Item);
  2. 你能否 手写一个适配器(adapter)并保证零成本抽象、惰性求值、链式调用,而不是只会用标准库现成方法。
    回答时务必把“零成本抽象”“惰性求值”“所有权传递”三个关键词抛出来,再给出完整可编译的代码,基本就能拿到高分。

知识点

  • Iterator traitfn next(&mut self) -> Option<Self::Item>,一旦返回 None 即终止。
  • 关联类型 Item:每次迭代吐出的值类型,由实现者指定,保证链式调用时类型安全。
  • 适配器模式:消费 self 返回新迭代器,不立即分配内存,惰性求值
  • Sized + 默认方法:标准库 90% 的 adaptor 都是 where Self: Sized 的默认方法,保证零成本。
  • 生命周期参数:若迭代器内部持有引用,必须显式标注 'a,否则借用检查器会报错。
  • FusedIterator:标记 trait,承诺 None 之后永远返回 None,优化器可据此去掉重复检查。
  • TrustedRandomAccess:nightly 专用,进一步向 LLVM 暴露 vectorization 信息,面试提到即可加分。

答案

下面给出 完全可编译、零成本、惰性求值 的“流式 trait”示例:实现一个 step_by_ref 适配器,对任意迭代器按引用步进,不克隆元素。

use std::iter::FusedIterator;

/// 1. 定义适配器:按引用步进
pub struct StepByRef<I> {
    iter: I,
    step: usize,
    // 记录已经跳过多少
    i: usize,
}

/// 2. 实现 Iterator,核心就是 next
impl<I> Iterator for StepByRef<I>
where
    I: Iterator,
{
    type Item = I::Item;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        // 先吐出当前
        let item = self.iter.next()?;
        // 再跳过 step-1 个
        for _ in 1..self.step {
            self.iter.next()?;
        }
        Some(item)
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let (low, high) = self.iter.size_hint();
        // 向下取整,保证保守
        let div = |x: usize| x / self.step + (x % self.step != 0) as usize;
        (div(low), high.map(div))
    }
}

/// 3. FusedIterator 标记,告诉编译器 None 后永不回退
impl<I: FusedIterator> FusedIterator for StepByRef<I> {}

/// 4. 给所有迭代器加一个“流式”方法
pub trait StepByRefExt: Iterator {
    fn step_by_ref(self, step: usize) -> StepByRef<Self>
    where
        Self: Sized,
    {
        assert!(step > 0);
        StepByRef { iter: self, step, i: 0 }
    }
}

/// 5.  blanket impl:所有迭代器都能用
impl<T: Iterator> StepByRefExt for T {}

/// 6. 使用示例:链式调用,零成本抽象
fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
    let sum: i32 = v
        .iter()
        .step_by_ref(3)   // 自定义 adaptor
        .map(|&&x| x * 2)
        .filter(|&x| x > 4)
        .sum();
    println!("{sum}"); // 输出 20
}

编译通过即正确,无堆分配、无动态分发、无运行时检查,完全符合 Rust 的零成本抽象理念。

拓展思考

  1. 异步流式 traitfutures::stream::StreamIterator 的异步版,核心方法是 poll_next。可以对比 Iterator::nextStream::poll_next 的签名差异,引出 Waker 机制与 Pin 的必要性。
  2. ** lending iterator**:目前稳定版无法表达“每次返回不同生命周期的引用”,需要 Generic Associated Type (GAT)。可提及 streaming-iterator crate 的设计痛点,展示你对语言演进的跟踪深度。
  3. 编译器优化:在 release 模式下,上述 StepByRef 会被 LLVM inline 并展开成 SIMD 指令;可配合 cargo asm 查看最终汇编,向面试官展示你“性能可预测”的素养。