首页
/ Rust 编译器错误 E0161 详解:为什么不能移动未知大小的值(dyn 类型)及解决方法

Rust 编译器错误 E0161 详解:为什么不能移动未知大小的值(dyn 类型)及解决方法

2026-09-06 17:00:31作者:虞亚竹Luna

E0161(cannot move a value of type ...)是 Rust 编译器在类型检查阶段报出的经典错误,触发原因是你试图"移动"(move)一个在编译期无法确定大小(unsized)的值,例如裸的 dyn Trait 对象。读完本文,你将理解该错误背后的 Sized 规则与 MIR 层面的检查机制,掌握通过引用、BoxRc 等方式绕过限制的完整方案,并能定位到 rustc 源码中产生该诊断的具体位置。

E0161 错误是什么

E0161 的完整诊断信息形如:

error[E0161]: cannot move a value of type `dyn Bar`
  --> src/main.rs:6:5
   |
LL |     b.f();
   |     ^ the size of `dyn Bar` cannot be statically determined

官方错误文档对该错误的定义是:尝试移动一个在编译期大小未知的值(a value was moved whose size was not known at compile time)。在 Rust 中,只有当类型在编译期大小已知(即实现了 Sized)时,值才能被直接移动、赋值或按值传递;dyn Trait、切片 [T]、C 风格字符串 str 这类动态大小类型不满足该前提。

触发错误的典型代码

官方错误文档 E0161.md 给出的原始示例是:

trait Bar {
    fn f(self);   // 按值消费 self
}

impl Bar for i32 {
    fn f(self) {}
}

fn main() {
    let b: Box<dyn Bar> = Box::new(0i32);
    b.f();
    // error[E0161]: cannot move a value of type `dyn Bar`:
    //            the size of `dyn Bar` cannot be statically determined
}

这里的关键在于方法 f 的接收者是 self(按值移动)。当你调用 b.f() 时,编译器需要将 b 解引用后按值移动 dyn Bar 这个本体。虽然 Box<dyn Bar> 本身是定长的(一个指针),但被移动的值类型是 dyn Bar 而非 Box<dyn Bar>——dyn Bar 的实际数据大小在编译期不可知,因此按值移动它无法生成合法代码,编译器直接报 E0161。

仓库中的 UI 测试 E0161.rs 验证了同一场景,并特意注释说明这是为了确认 E0161 在任何可能影响它的配置下都是硬错误:

// Check that E0161 is a hard error in all possible configurations that might
// affect it.

#![crate_type = "lib"]

trait Bar {
    fn f(self);
}

fn foo(x: Box<dyn Bar>) {
    x.f();
    //~^ ERROR E0161
}

对应的期望诊断输出见 E0161.stderr

error[E0161]: cannot move a value of type `dyn Bar`
  --> $DIR/E0161.rs:11:5
   |
LL |     x.f();
   |     ^ the size of `dyn Bar` cannot be statically determined

注意错误 span 指向的是调用语句 x.f(); 整体,而不是 dyn 类型出现的位置——这一点与源码中的检查点位置一致,下文会说明。

官方推荐的修复方案:把值藏在引用后面

错误文档给出的解决方法是:"将值隐藏在引用后面"——使用 &x&mut x。引用的大小是固定的(普通指针 8 字节、胖指针 16 字节),因此可以像普通值一样自由移动和传递。具体做法是把 trait 方法的接收者从 self 改为 &self

trait Bar {
    fn f(&self);   // 改为借用 self
}

impl Bar for i32 {
    fn f(&self) {}
}

fn main() {
    let b: Box<dyn Bar> = Box::new(0i32);
    b.f();   // ok!
}

此时 b.f() 实际执行的是 Bar::f(&*b):先解引用得到 dyn Bar 的借用,再传入定长的引用,全程没有移动 dyn Bar 本体,编译通过。

如果方法确实需要消费对象(比如要取出内部所有权),正确姿势是移动"指针"而不是移动"本体":

trait Bar {
    fn f(self);            // 保持按值语义
}

impl Bar for i32 {
    fn f(self) {}
}

fn main() {
    let b: Box<dyn Bar> = Box::new(0i32);
    Box::into_inner(b).f(); // 移动 Box(定长指针),而不是 dyn Bar
    // 或者把参数类型声明为 Box<dyn Bar>,由调用方负责解包
}

同理,函数参数、返回值中也不能直接出现裸的 dyn Trait(这通常是另一个更早期的 unsized type 相关报错),应写成 &dyn TraitBox<dyn Trait>Rc<dyn Trait> 等定长包装形式。

源码级解析:E0161 在哪里、如何产生

E0161 的诊断结构体定义在 session_diagnostics.rs

#[derive(Diagnostic)]
#[diag("cannot move a value of type `{$ty}`", code = E0161)]
pub(crate) struct MoveUnsized<'tcx> {
    pub ty: Ty<'tcx>,
    #[primary_span]
    #[label("the size of `{$ty}` cannot be statically determined")]
    pub span: Span,
}

两个 diag/label 字符串正好对应你在终端看到的"主错误行 + ^ 下划线处的补充说明",与 E0161.stderr 中的输出逐字一致。

真正触发该诊断的是 MIR 类型检查中的 ensure_place_sized 方法,位于 type_check/mod.rs

fn ensure_place_sized(&mut self, ty: Ty<'tcx>, span: Span) {
    let tcx = self.tcx();

    // Erase the regions from `ty` to get a global type. ...
    let erased_ty = tcx.erase_and_anonymize_regions(ty);
    // FIXME(#132279): Using `Ty::is_sized` causes us to incorrectly handle opaques here.
    if !erased_ty.is_sized(tcx, self.infcx.typing_env(self.infcx.param_env)) {
        // in current MIR construction, all non-control-flow rvalue
        // expressions evaluate through `as_temp` or `into` a return
        // slot or local, so to find all unsized rvalues it is enough
        // to check all temps, return slots and locals.
        if self.reported_errors.replace((ty, span)).is_none() {
            // While this is located in `nll::typeck` this error is not
            // an NLL error, it's a required check to prevent creation
            // of unsized rvalues in a call expression.
            self.tcx().dcx().emit_err(MoveUnsized { ty, span });
        }
    }
}

从源码结构看,检查逻辑包含三个要点:

  1. 判据是 Sized 而非字面"大小":先把类型中的区域(lifetime)擦除得到全局类型(Sized 判定与精确的 lifetime 无关),然后调用 is_sized 判断。只要类型不满足 Sized,且该类型出现在一个"值被求值/存放"的位置(临时变量、返回值槽、局部变量),就会报 E0161。
  2. 检查点覆盖所有临时对象(temps):同文件上方 type_check/mod.rs 中,遍历 LocalKind::Temp 声明时逐一调用 ensure_place_sized。源码注释解释了为什么这样做就够:当前 MIR 构造中,所有非控制流的 rvalue 表达式都会经过 as_temp 或求值进返回槽/局部变量,所以只要检查所有 temps、返回槽和局部变量,就能覆盖全部 unsized rvalue——b.f() 这种按值调用生成的临时求值对象正落入此列。
  3. 同一位置只报一次reported_errors.replace(...) 的去重机制保证同一处 unsized 类型不会重复刷屏,且注释明确指出该检查虽然位于 borrowck(NLL 类型检查)中,但本质上是防止"在调用表达式中创建 unsized rvalue"的必备检查,不属于借用检查规则本身。

这解释了测试输出中 span 指向整条调用语句的现象:错误 span 来自生成该临时对象的 MIR 位置信息(local_decl.source_info.span),而非类型标注处。

适用前提与限制

  • 本错误是硬错误,不依赖任何 feature gate;E0161.rs 的注释表明团队专门验证了它在各种可能相关的配置下(如不同 crate_type)始终成立。
  • 源码中 ensure_place_sized 存在一个已知的 FIXME(引用问题 #132279):当前实现对 opaque 类型(如 impl Trait 背后的类型)的 is_sized 处理尚不完美,可以推断这类边缘情况下的诊断行为可能随编译器版本演进。
  • 当 unstable feature unsized_fn_params 启用时,对参数位置的检查会转移到终止符(terminator)路径上(见 type_check/mod.rsif self.tcx().features().unsized_fn_params() 分支),但在稳定版行为不受影响。

总结

项目 内容
错误码 E0161
触发条件 尝试移动/求值一个不满足 Sized 的值(如裸 dyn Trait[T]str
诊断来源 MoveUnsized,由 ensure_place_sized 在 MIR 类型检查中发出
首选修复 self 方法改为 &self(或 &mut self),通过引用传递
需要消费时 传递/解包定长智能指针,如 Box<dyn Trait>Rc<dyn Trait>,而不是裸 dyn Trait
参考测试 tests/ui/error-codes/E0161.rsE0161.stderr

核心记忆点一句话:Rust 只允许移动编译期大小已知的值;遇到 E0161 时,把"移动本体"改为"移动指向本体的定长指针(引用或智能指针)"即可。

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

项目优选

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