首页
/ Rust 编译器错误 E0070 详解:为什么赋值语句左侧必须是 place 表达式

Rust 编译器错误 E0070 详解:为什么赋值语句左侧必须是 place 表达式

2026-09-06 13:20:55作者:邵娇湘

本文围绕 rustc 错误码 E0070(invalid left-hand side of assignment,赋值语句的左侧不是有效的赋值目标)展开,基于 rust 编译器仓库中的错误码文档 E0070.md、真实 UI 测试用例 E0070.rs 及其期望输出 E0070.stderr,并结合类型检查器源码中 E0070 的实际触发点,完整讲解"place 表达式"的语义边界、四类典型报错场景、可编译的正确写法,以及诊断信息在 rustc_hir_typeck 中的生成链路。读完本文后,你可以准确判断任何赋值表达式左侧是否合法,并快速定位 E0070 的根因与修复方式。

一、E0070 的语义:对"非 place 表达式"执行了赋值

E0070 的错误标题是 An assignment operator was used on a non-place expression(赋值运算符被用于一个非 place 表达式)。

在 Rust 中,赋值语句 lhs = rhs 要求左侧(left-hand side, lhs)必须是一个 place 表达式。place 表达式代表一个可寻址的内存位置,根据错误码文档的定义,它可以是:

  • 变量(可带命名空间限定),如 xlocal.y
  • 解引用(deref),如 *p
  • 索引表达式,如 v[i]arr[0]
  • 字段引用(field reference),如 s.x

只要左侧不满足上述任一形态——比如常量、字面量、函数调用、类型/结构体名——rustc 就会报告 E0070。文档同时指出,更多细节可参考 Reference 的 Expressions 章节("places, rvalues and temporaries" 部分):Rust 将表达式严格区分为 place(可寻址)、rvalue(临时值)与 temporary,E0070 正是这一区分的直接体现。

1.1 典型的错误写法

错误码文档中给出的完整错误示例(标注了 compile_fail,E0070)如下,四种赋值目标全部非法:

// 对应仓库测试 tests/ui/error-codes/E0070.rs 的扩展版本
struct SomeStruct {
    x: i32,
    y: i32,
}

const SOME_CONST: i32 = 12;

fn some_other_func() {}

fn some_function() {
    SOME_CONST = 14;        // error: a constant value cannot be changed!
    1 = 3;                  // error: 1 isn't a valid place!
    some_other_func() = 4;   // error: we cannot assign value to a function!
    SomeStruct::x = 12;      // error: SomeStruct is a structure name but
                             //        it is used like a variable!
}

四类报错场景分别对应不同的"非 place"来源:

左侧写法 为什么不是 place 直观解释
SOME_CONST 常量绑定不是可变内存位置 常量值不可改变
1(字面量) 字面量是 rvalue,无内存地址 1 根本不是存储位置
some_other_func() 函数调用结果是临时值(rvalue) 不能给一个"值"赋值
SomeStruct::x 结构体类型名不是变量 类型/结构名被当成了变量

1.2 编译器实际输出的诊断信息

仓库中的 UI 测试 E0070.rs 保留了前三类场景,其期望诊断输出 E0070.stderr 展示了 rustc 真实的报错格式:

error[E0070]: invalid left-hand side of assignment
  --> $DIR/E0070.rs:6:16
   |
LL |     SOME_CONST = 14;
   |     ---------- ^
   |     |
   |     cannot assign to this expression

error[E0070]: invalid left-hand side of assignment
  --> $DIR/E0070.rs:7:7
   |
LL |     1 = 3;
   |     - ^
   |     |
   |     cannot assign to this expression

error[E0070]: invalid left-hand side of assignment
  --> $DIR/E0070.rs:8:23
   |
LL |     some_other_func() = 4;
   |     ----------------- ^
   |     |
   |     cannot assign to this expression

可以看到诊断的固定结构:主标题为 invalid left-hand side of assignment,子标签 cannot assign to this expression 指向 = 右侧位置;SomeStruct::x 这一变体没有包含在该测试文件中,属于文档中额外补充的第四类场景。这也说明错误码文档与 UI 测试用例是相互印证的两份证据来源。

二、合法写法:四类 place 表达式逐一对照

错误码文档给出的"可运行示例"覆盖了三类合法 place——字段赋值与解引用赋值(变量本身是最基础的一类):

struct SomeStruct {
    x: i32,
    y: i32,
}

// 原文档示例整理为可编译形式(增加 main 与函数包装)
fn main() {
    let mut s = SomeStruct { x: 0, y: 0 };

    s.x = 3; // that's good ! —— 字段引用是 place

    // ...
}

fn some_func(x: &mut i32) {
    *x = 12; // that's good ! —— 解引用是 place
}

说明:原文档代码片段中 let mut s = ... 直接位于顶层,这里仅为其加上 fn main 包装使其真正可编译,赋值语句本身与原文档完全一致。

结合测试文件与参考语义,四类 place 完整对照如下:

// 1. 变量(可选命名空间)
fn f() {
    let mut x: i32 = 0;
    x = 1; // OK:局部变量
}

// 2. 解引用
fn g(p: &mut i32) {
    *p = 2; // OK:通过引用改写目标内存
}

// 3. 索引表达式
fn h(v: &mut Vec<i32>) {
    v[0] = 3; // OK:索引表达式是 place
}

// 4. 字段引用
fn i(s: &mut SomeStruct) {
    s.y = 4; // OK:结构体字段
}

一个容易踩的坑:mut 是前提。place 表达式的"可赋值性"还要求底层绑定可变(let mut&mut 等)。如果左侧是 place 但底层不可变,rustc 报告的将不是 E0070,而是 E0384(cannot assign to immutable variable)——这属于另一条诊断路径。

三、源码级溯源:E0070 在 rustc 中如何被触发

从源码结构看,E0070 的诊断生成集中在 HIR 类型检查器 rustc_hir_typeck 中:

3.1 赋值检查入口:check_lhs_assignable

expr.rs 中定义了 check_lhs_assignable 方法,它是所有"赋值类"语句共用的左侧合法性校验入口。在普通赋值表达式(lhs = rhs)的检查流程中,完成右值类型检查与强制转换后,编译器显式调用该方法并传入错误码 E0070

// compiler/rustc_hir_typeck/src/expr.rs (约 L1305)
self.check_lhs_assignable(lhs, E0070, span, |err| {
    if let Some(rhs_ty) = self.typeck_results.borrow().expr_ty_opt(rhs) {
        suggest_deref_binop(err, rhs_ty);
    }
});

值得注意的是这个调用附带的回调:当左侧不可赋值时,如果右侧类型允许,诊断会尝试给出"解引用二元运算"的修复建议(suggest_deref_binop),即在报错的同时机器可应用地建议用户补一个 *

check_lhs_assignable 内部构造的正是测试文件中看到的那条诊断,主标题在 expr.rs 附近生成:

let mut err = self.dcx().struct_span_err(op_span, "invalid left-hand side of assignment");

随后补充 cannot assign to this expression 标签,与 E0070.stderr 中的输出逐字对应。

3.2 同一错误码在其他赋值形态中复用

E0070 并不只在 = 表达式中出现。搜索 E0070 可以看到它在多个赋值相关检查中被复用:

  • fn_ctxt/checks.rs:在处理语句级赋值/复合赋值等场景时,源码注释说明"如果左侧不是句法层面的 place 表达式,E0070 会先行触发,此处冗余错误会被静默抑制"——这解释了为什么一条非法语句通常只报一次 E0070 而不是级联出多条错误;
  • demand.rs:类型需求(type demand)路径中的注释同样声明"已发出 E0070 'invalid left-hand side of assignment'",用于避免对同一位置重复诊断。

可以推断,编译器刻意用"首个 E0070 胜出、后续冗余诊断静默"的策略保证报错的可读性——这与测试输出中每条非法赋值恰好对应一条 error 的行为一致。

四、排查与修复清单

遇到 error[E0070]: invalid left-hand side of assignment 时,按以下顺序排查:

  1. 确认左侧是否落入四类 place:变量、解引用、索引、字段引用。字面量(1 = 3)、常量(SOME_CONST = 14)、函数调用(f() = 4)、类型/结构名(SomeStruct::x = 12)都非法,直接改写为合法的存储位置;
  2. 区分"不可变"与"非 place":如果左侧是变量但缺少 mut,报的是 E0384 而非 E0070;E0070 意味着左侧根本没有对应的内存位置;
  3. 函数调用返回引用时:若本意是 f() = 4f() 返回 &mut T,可先绑定到变量再赋值:let r = f(); *r = 4;——这符合编译器 suggest_deref_binop 的建议方向;
  4. 验证方式:仓库中 tests/ui/error-codes/E0070.rs 展示了 rustc 对每类非法左侧的标准诊断;修改代码后可直接 rustc --explain E0070 查看与本节一致的解释文档。

五、小结

E0070 是 Rust 类型系统对"赋值必须指向可寻址位置"这一原则的守卫:左侧必须是变量、解引用、索引或字段引用这四类 place 表达式之一,常量、字面量、函数调用与类型名都不合格。错误码文档 E0070.md 给出了全部典型反例与正例,UI 测试 E0070.stderr 固化了真实诊断文本,而 rustc_hir_typeckcheck_lhs_assignable 的调用点 及其冗余诊断抑制逻辑则说明了该错误码"只报一次、附带修复建议"的行为来源。理解了 place 与 rvalue 的边界,E0070 的每一种触发场景与修复路径就都可以直接推导出来。

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

项目优选

收起
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