news 2026/9/7 10:36:16

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

作者头像

张小明

前端开发工程师

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

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

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

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 featureunsized_fn_params启用时,对参数位置的检查会转移到终止符(terminator)路径上(见 type_check/mod.rs 中if 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.rs、E0161.stderr

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

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 10:35:46

全面解析检索技术:全景图与深度分析

目录 一、必要性分析 二、现代业务系统应用举例 三、简单的知识全景图分析 (一)存储介质的选择 (二)数据结构与算法层 (三)检索专业知识 工程架构 算法策略 QP策略 召回策略算法 粗排算法 常见的粗排算法 精排算法 加权评分策略算法 过滤策略算法 重排策略…

作者头像 李华
网站建设 2026/9/7 10:34:19

人脸表情识别模型包实操:从解压到推理的完整指南

简介&#xff1a;面向深度学习、计算机视觉与PyTorch开发者的人脸表情识别项目模型包&#xff0c;提供训练好的三种经典网络权重&#xff0c;可直接用于表情分类推理、迁移学习或学术复现。压缩包内共5个文件&#xff0c;以pkl模型文件为主&#xff0c;涵盖CNN、VGG、ResNet三种…

作者头像 李华
网站建设 2026/9/7 10:33:38

STM32F103编码器接口开发实战:从原理到调试的完整指南

简介&#xff1a;面向STM32F103嵌入式开发者的增量式编码器程序资源&#xff0c;用于解决电机控制、机器人定位、自动化设备等场景中的位置与速度采集难题&#xff0c;适合正在学习定时器编码器接口的初中级开发者。压缩包共934个文件&#xff0c;以C源码和头文件为主&#xff…

作者头像 李华
网站建设 2026/9/7 10:33:04

AI 成吃电大魔王,LG 等电池厂商弃汽车业务转投储能赛道!

【导语&#xff1a;AI 基建的发展让其成为超级吃电大户&#xff0c;美国电网难以满足其用电需求&#xff0c;储能成为解决缺电问题的关键。LG 等电池厂商纷纷转投储能电池生产&#xff0c;新能源车电池市场或因此燃起新战火。】LG 转身&#xff1a;从汽车电池到储能电池LG 作为…

作者头像 李华
网站建设 2026/9/7 10:32:44

任意轴旋转体体积:垫片法、壳层法与坐标变换详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华