如何 yield 语法?

解读

国内 Rust 面试里问“如何 yield 语法”,并不是要你背 yield 这个关键字——Rust 根本没有 yield 关键字。
面试官真正想看的是:

  1. 你是否知道 Rust 用 生成器(Generator) 语法块 || { ... yield expr; ... } 来实现“协程级”惰性产出;
  2. 你是否清楚 生成器目前只在 nightly 通道 可用,stable 下必须依赖 async/await + Stream 或手写状态机;
  3. 你是否能给出 零成本、线程安全 的实战写法,而不是照搬 JavaScript 的 yield 语义。
    答不到这三层,基本会被判“只看过语法糖,没写过生产代码”。

知识点

  • Generator trait(nightly):std::ops::Generator + yield 语法糖,编译器自动生成状态机。
  • Pin/Resume 语义:unsafe pinned 保证生成器内部自引用安全;resume() 返回 GeneratorState<Yield, Return>
  • Stable 替代方案futures::stream::unfold、手动 enum State 状态机、或 async-stream 宏。
  • 零成本抽象:生成器在编译期展开为 switch 状态机,无运行时调度器开销,内存布局等价于手写状态机。
  • 内存安全:借用检查器在 yield 点跟踪跨暂停生命周期,杜绝悬垂引用;Send/Sync 自动推导保证线程安全。
  • 实战限制:nightly 生成器不能跨 .await 点保存引用,stable 代码库(tokio、hyper)全部用 Stream 而非 yield

答案

“Rust 没有 yield 关键字,官方用 生成器语法块 在 nightly 实现惰性产出:

#![feature(generators, generator_trait)]
use std::{ops::{Generator, GeneratorState}, pin::Pin};

fn main() {
    let mut gen = || {
        yield 1;
        yield 2;
        return "done";
    };
    let mut pinned = Pin::new(&mut gen);
    loop {
        match pinned.as_mut().resume(()) {
            GeneratorState::Yielded(v) => println!("{v}"),
            GeneratorState::Complete(ret) => { println!("{ret}"); break; }
        }
    }
}

stable 通道,我们不用 yield,而是:

  1. async-stream 宏:
use async_stream::stream;
use futures_util::stream::StreamExt;

#[tokio::main]
async fn main() {
    let s = stream! {
        for i in 1..=3 {
            yield i * 2;
        }
    };
    s.for_each(|x| async move { println!("{x}"); }).await;
}
  1. 或者手写 Stream 状态机,保证 零成本、无额外分配
    总结:Rust 通过编译期状态机实现 内存安全、无 GC、零成本yield 语义,stable 下推荐 Stream 生态,nightly 下可直接用 yield 语法块。”

拓展思考

  • 如果面试官追问“生成器为什么不能直接用于 for 循环”,可答:生成器目前 未实现 Iterator trait,需要包装一层 gen-iter 适配器;社区 RFC 已提出 gen fn 语法,未来可能稳定。
  • 若问题延伸到 异步生成器,可补充:async gen fn 正在 nightly 实验,解决 yield.await 混用时的借用冲突,目标是让 tokio::fs 等库用同步写法产出 Stream,性能对标手写状态机。
  • 嵌入式场景内存极紧张,可手写 const 泛型状态机,用 enum + #[inline(always)]yield 展开为单字节状态变量,ROM 占用比 C 语言 switch 更小,同时保持 Rust 的内存安全保证。