首页
/ Rust 编译器错误 E0627 详解:yield 表达式出现在协程字面量之外

Rust 编译器错误 E0627 详解:yield 表达式出现在协程字面量之外

2026-09-08 19:43:55作者:蔡怀权

导读

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.rsExprKind 枚举的注释:

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_typesNone(即当前不在任何协程字面量内)时,直接通过 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"
    };
}

修复要点说明:

  1. 把普通函数改为闭包:具名函数无法被标记为协程,必须使用闭包字面量;
  2. 为闭包添加 #[coroutine] 属性:该属性将闭包显式标记为协程,使其内部可以使用 yield
  3. 保留特性开关:协程目前仍是实验特性,需要在 crate 顶部声明 #![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
  4. 变量需要 mut:协程被调用时其内部状态会发生改变(挂起/恢复),因此绑定需要可变性。

修正后,yield 1 表示协程第一次被 poll 时挂起并产出 1yield_tyi32),恢复后继续执行 return "foo"return_ty&'static str)。

4.1 其他合法的 yield 上下文

根据 rustc 的特性门控实现(见 compiler/rustc_ast_passes/src/feature_gate.rs 附近对 coroutines / gen_blocksyield_expr 关系的注释),目前 yield 合法的上下文还包括:

  • gen:生成器块(generator block),同样为实验特性,feature gate 声明在 compiler/rustc_ast_passes/src/feature_gate.rsgate_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.stderrtests/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 时,可以按以下步骤排查:

  1. 定位 yield 位置:检查报错 span 指向的 yield 表达式,确认它所在的最小代码单元;
  2. 确认上下文:该 yield 是否位于 #[coroutine] 闭包或 gen 块(实验特性)内部?如果不在,把外层结构改造成协程字面量(将具名函数改写为 #[coroutine] 闭包,或将块改写为 gen 块);
  3. 确认特性声明:crate 顶部是否声明了 #![feature(coroutines, coroutine_trait, stmt_expr_attributes)](使用 gen 块时还需要 #![feature(gen_blocks)]);
  4. 使用 explain 查看官方说明:直接运行 rustc --explain E0627,或在错误输出中查看 For more information about this error, try rustc --explain E0627. 的指引;
  5. 对照仓库测试:可参考 tests/ui/coroutine/ 目录下大量协程相关测试(如 yield-in-function.rsyield-outside-coroutine-issue-78653.rs)理解各种合法与非法场景的期望行为。

七、总结

E0627 是 rustc 在类型检查阶段用于保护协程语义的错误代码,它确保 yield 表达式只出现在协程字面量内部。其底层实现位于 compiler/rustc_hir_typeck/src/expr.rscheck_expr_yield,诊断定义与错误文档则分别位于 diagnostics.rsE0627.md。修复该错误的核心手段只有一个:把使用 yield 的代码放进 #[coroutine] 闭包或 gen 块这样的协程字面量中,并正确声明实验特性开关。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391