Rust 编译器错误 E0627 详解:yield 表达式出现在协程字面量之外
导读
E0627 是 Rust 编译器(rustc)在类型检查阶段报出的错误代码,含义是"yield 表达式被使用在了协程字面量(coroutine literal)之外"。本文以 rustc 源码仓库中的 E0627 错误代码文档 为骨架,结合其触发点源码与 UI 测试用例,系统讲解该错误的成因、复现方式、修复方法,以及它与其他 yield 相关错误(如特性门控错误)的区别。读完本文,你将能准确理解 yield 关键字的合法使用上下文,并熟练写出正确的协程(coroutine)代码。
一、错误概述:E0627 是什么
根据 E0627.md 的定义,该错误的完整表述为:
A yield expression was used outside of the coroutine literal.
即:yield 表达式只能出现在协程字面量内部。任何在普通函数、普通闭包、常量或静态变量等非协程上下文中使用 yield 的代码,都会触发该错误。
rustc 会同时在终端输出一行简短提示:
error[E0627]: yield expression outside of coroutine literal
二、错误示例:在普通函数中使用 yield
下面是文档中给出的官方错误示例(compile_fail 测试用例):
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
fn fake_coroutine() -> &'static str {
yield 1;
return "foo"
}
fn main() {
let mut coroutine = fake_coroutine;
}
这段代码的意图显然是"假装"写一个协程:在一个普通具名函数 fake_coroutine 内部使用了 yield 1。然而,具名函数不是协程字面量,yield 在函数体内没有任何合法的挂起语义,因此编译器拒绝编译。
该示例在仓库的 UI 测试中也有对应的最小化版本 tests/ui/coroutine/yield-in-function.rs:
#![feature(coroutines)]
fn main() { yield; }
//~^ ERROR yield expression outside
//~| ERROR `yield` can only be used in
其期望输出文件 tests/ui/coroutine/yield-in-function.stderr 展示了真实的编译诊断:
error: `yield` can only be used in `#[coroutine]` closures, or `gen` blocks
--> $DIR/yield-in-function.rs:3:13
|
LL | fn main() { yield; }
| ^^^^^
|
help: use `#[coroutine]` to make this closure a coroutine
|
LL | #[coroutine] fn main() { yield; }
| ++++++++++++
error[E0627]: yield expression outside of coroutine literal
--> $DIR/yield-in-function.rs:3:13
|
LL | fn main() { yield; }
| ^^^^^
error: aborting due to 2 previous errors
可以看到,一次非法使用 yield 会同时产生两条错误:一条是特性门控(feature gate)检查给出的提示(yield 只能用于 #[coroutine] 闭包或 gen 块),另一条才是 E0627 本身。
三、错误成因:yield 为什么必须限定在协程字面量内
3.1 yield 的本质:协程的挂起点
在 rustc 的 HIR 定义中,yield 被明确定义为协程的挂起点。见 compiler/rustc_hir/src/hir.rs 中 ExprKind 枚举的注释:
A suspension point for coroutines (i.e.,
yield <expr>).
也就是说,yield 表达式的语义是"将当前执行挂起,并把 <expr> 的值产出给调用方,等待下一次 resume"。这种挂起/恢复机制只有在一个被降级为协程状态机的闭包或块内才有意义。普通函数是一次性执行到底的,没有挂起机制,因此 yield 在其中没有合法含义。
3.2 源码触发点:check_expr_yield
E0627 在类型检查阶段被触发。类型检查器在处理 yield 表达式时调用 check_expr_yield,其实现位于 compiler/rustc_hir_typeck/src/expr.rs:
fn check_expr_yield(
&self,
value: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
match self.coroutine_types {
Some(CoroutineTypes { resume_ty, yield_ty }) => {
self.check_expr_coercible_to_type(value, yield_ty, None);
resume_ty
}
_ => {
self.dcx().emit_err(YieldExprOutsideOfCoroutine { span: expr.span });
// Avoid expressions without types during writeback (#78653).
self.check_expr(value);
self.tcx.types.unit
}
}
}
从源码结构看,其判定逻辑非常清晰:
- 当类型检查上下文带有
coroutine_types(即当前正处于协程字面量内部)时,yield的产出值会被强制转换为协程的yield_ty,整个yield表达式的类型是resume_ty; - 当
coroutine_types为None(即当前不在任何协程字面量内)时,直接通过emit_err发出 E0627 诊断,并把表达式类型临时设为()。
值得注意的是源码中的一行注释:// Avoid expressions without types during writeback (#78653).,它对应测试 tests/ui/coroutine/yield-outside-coroutine-issue-78653.rs(该测试还同时验证了 yield 右侧表达式会继续被检查、以及 {integer} 不是迭代器等附加错误)。这说明即使 yield 本身非法,编译器仍会继续检查其子表达式,以避免回写(writeback)阶段出现"表达式无类型"的级联崩溃——这是错误恢复(error recovery)机制的一部分。
3.3 诊断结构体定义
E0627 对应的诊断结构体定义在 compiler/rustc_hir_typeck/src/diagnostics.rs:
#[derive(Diagnostic)]
#[diag("yield expression outside of coroutine literal", code = E0627)]
pub(crate) struct YieldExprOutsideOfCoroutine {
#[primary_span]
pub span: Span,
}
rustc 采用结构化的 #[diag(...)] 声明式诊断体系,这里的 code = E0627 把错误代码与 rustc_error_codes 中的 E0627.md 文档一一对应起来。当用户运行 rustc --explain E0627 时,编译器展示的正是该文档的正文。
四、修复方法:正确构造协程字面量
文档给出的正确示例是使用 #[coroutine] 属性修饰一个闭包字面量:
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
fn main() {
let mut coroutine = #[coroutine] || {
yield 1;
return "foo"
};
}
修复要点说明:
- 把普通函数改为闭包:具名函数无法被标记为协程,必须使用闭包字面量;
- 为闭包添加
#[coroutine]属性:该属性将闭包显式标记为协程,使其内部可以使用yield; - 保留特性开关:协程目前仍是实验特性,需要在 crate 顶部声明
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]; - 变量需要
mut:协程被调用时其内部状态会发生改变(挂起/恢复),因此绑定需要可变性。
修正后,yield 1 表示协程第一次被 poll 时挂起并产出 1(yield_ty 为 i32),恢复后继续执行 return "foo"(return_ty 为 &'static str)。
4.1 其他合法的 yield 上下文
根据 rustc 的特性门控实现(见 compiler/rustc_ast_passes/src/feature_gate.rs 附近对 coroutines / gen_blocks 与 yield_expr 关系的注释),目前 yield 合法的上下文还包括:
gen块:生成器块(generator block),同样为实验特性,feature gate 声明在 compiler/rustc_ast_passes/src/feature_gate.rs(gate_all!(gen_blocks, "gen blocks are experimental"));async块/闭包内部:async上下文中yield是合法的(对应 HIR 中CoroutineKind为 async 的协程),但异步协程的yield_ty为(),通常配合gen块形成"异步生成器"(可参考 tests/ui/coroutine/async-gen-deduce-yield.rs 等测试)。
需要强调的是,普通具名函数、const 块、static 初始化、普通闭包都不属于协程字面量。仓库测试 tests/ui/coroutine/yield-in-const.stderr、tests/ui/coroutine/yield-in-static.stderr 以及 issue 91477(tests/ui/coroutine/issue-91477.rs)分别验证了这些场景下的报错行为。
五、E0627 与相关错误的辨析
在排查 yield 相关编译错误时,需要注意区分以下两个常一起出现的错误:
| 错误 | 阶段 | 含义 |
|---|---|---|
| E0627 | 类型检查(HIR typeck) | yield 出现在协程字面量之外,属于上下文合法性错误 |
| 特性门控错误(无错误码) | AST 阶段(feature gate) | yield 相关特性未启用,或 yield 被用在完全不允许的位置(此时文本提示为 "yield can only be used in #[coroutine] closures, or gen blocks",并带有添加 #[coroutine] 的 help 建议) |
正如 tests/ui/coroutine/yield-in-function.stderr 所示,在未启用特性时于普通函数中使用 yield,会先由 AST 阶段的特性检查给出"只能在 #[coroutine] 闭包或 gen 块中使用"的提示,随后由类型检查阶段给出 E0627。二者分工不同:前者校验语法位置与特性开关,后者校验类型层面的协程上下文。
此外,即使 yield 位于协程字面量内部,也可能触发其他错误码,例如借用检查阶段的"跨 yield 借用"(cannot_borrow_across_coroutine_yield,见 compiler/rustc_borrowck/src/borrowck_errors.rs),这类问题不在 E0627 的范围内。
六、快速定位与调试建议
当你在自己的代码中看到 E0627 时,可以按以下步骤排查:
- 定位 yield 位置:检查报错 span 指向的
yield表达式,确认它所在的最小代码单元; - 确认上下文:该
yield是否位于#[coroutine]闭包或gen块(实验特性)内部?如果不在,把外层结构改造成协程字面量(将具名函数改写为#[coroutine]闭包,或将块改写为gen块); - 确认特性声明:crate 顶部是否声明了
#]); - 使用 explain 查看官方说明:直接运行
rustc --explain E0627,或在错误输出中查看For more information about this error, try rustc --explain E0627.的指引; - 对照仓库测试:可参考 tests/ui/coroutine/ 目录下大量协程相关测试(如 yield-in-function.rs、yield-outside-coroutine-issue-78653.rs)理解各种合法与非法场景的期望行为。
七、总结
E0627 是 rustc 在类型检查阶段用于保护协程语义的错误代码,它确保 yield 表达式只出现在协程字面量内部。其底层实现位于 compiler/rustc_hir_typeck/src/expr.rs 的 check_expr_yield,诊断定义与错误文档则分别位于 diagnostics.rs 与 E0627.md。修复该错误的核心手段只有一个:把使用 yield 的代码放进 #[coroutine] 闭包或 gen 块这样的协程字面量中,并正确声明实验特性开关。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00