如何 yield 语法?
解读
国内 Rust 面试里问“如何 yield 语法”,并不是要你背 yield 这个关键字——Rust 根本没有 yield 关键字。
面试官真正想看的是:
- 你是否知道 Rust 用 生成器(Generator) 语法块
|| { ... yield expr; ... }来实现“协程级”惰性产出; - 你是否清楚 生成器目前只在 nightly 通道 可用,stable 下必须依赖 async/await + Stream 或手写状态机;
- 你是否能给出 零成本、线程安全 的实战写法,而不是照搬 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,而是:
- 用
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;
}
- 或者手写
Stream状态机,保证 零成本、无额外分配。
总结:Rust 通过编译期状态机实现 内存安全、无 GC、零成本 的yield语义,stable 下推荐Stream生态,nightly 下可直接用yield语法块。”
拓展思考
- 如果面试官追问“生成器为什么不能直接用于
for循环”,可答:生成器目前 未实现Iteratortrait,需要包装一层gen-iter适配器;社区 RFC 已提出gen fn语法,未来可能稳定。 - 若问题延伸到 异步生成器,可补充:
async gen fn正在 nightly 实验,解决yield与.await混用时的借用冲突,目标是让tokio::fs等库用同步写法产出Stream,性能对标手写状态机。 - 嵌入式场景内存极紧张,可手写 const 泛型状态机,用
enum+#[inline(always)]把yield展开为单字节状态变量,ROM 占用比 C 语言switch更小,同时保持 Rust 的内存安全保证。