Rust 编译错误 E0595 全解:闭包修改不可变捕获变量的历史与去向(已在 rustc 中退役并迁移至 E0594)
2026/9/9 23:27:03 网站建设 项目流程

Rust 编译错误 E0595 全解:闭包修改不可变捕获变量的历史与去向(已在 rustc 中退役并迁移至 E0594)

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

导读

在 Rust 编译器的错误码文档体系(compiler/rustc_error_codes/src/error_codes/)中,每个错误码都有一个独立文档条目,E0595 正是其中之一。它记录了一个极具代表性的借用检查错误:闭包(closure)试图修改被捕获(capture)的不可变变量。需要注意的是,这篇文档第一行就声明this error code is no longer emitted by the compiler——E0595 是一个已经退役的错误码,如今同样的代码会改由 E0594 报告。本文将以该文档为线索,厘清 E0595 的原始语义、退役后的实际报告路径(rustc_borrowck的 mutability 诊断体系)、对应修复方法,以及它与 E0594/E0596/E0384 等邻近错误码的边界,帮助你准确理解闭包捕获与变量可变性之间的编译期约束。

E0595 条目本身说了什么

E0595.md 的完整正文非常精炼,可拆解为三点:

  1. 状态声明:该错误码已不再由编译器发出(no longer emitted);
  2. 历史语义:闭包不能修改被捕获的不可变变量(Closures cannot mutate immutable captured variables);
  3. 错误示例与修复
let x = 3; // 错误:closure cannot assign to immutable local variable `x` let mut c = || { x += 1 };

修复方式是让被捕获的变量绑定可变:

let mut x = 3; // ok! let mut c = || { x += 1 };

注意示例块上的属性是compile_fail,E0594而非E0595,这正是“E0595 已退役、场景移交给 E0594”的直接体现——文档作者在保留历史讲解的同时,把可执行示例的期望错误码指向了当前实际报告方。

为什么 E0595 退役:它被并入了 E0594

搜索整个compiler/目录可以发现:E0595除了本文档自身之外没有任何源码引用;而E0594仍然活跃于借用检查器(borrowck)的诊断代码中,说明 E0595 对应的场景后来统一收编进 E0594 的消息体系。

历史上的 E0595

E0595 时代,下面的代码会触发E0595: closure cannot assign to immutable local variable x

let x = 3; let mut c = || { x += 1 }; // 闭包捕获 x,但绑定不可变

捕获(capture)≠ 可变性(mutability)|| { x += 1 }中的闭包体按引用捕获了x,对x执行+= 1属于写入操作。编译器要求在写入时该内存位置的“所有者绑定”是可变的——这正是借用检查器(MIR borrow check)中 mutability 检查的职责。既然let x没有mut,就构成对不可变位置的赋值错误。

现在的 E0594 接管

在 borrowck_errors.rs 中,borrow check 的 mutability 错误统一通过一个构造器生成:

pub(crate) fn cannot_assign(&self, span: Span, desc: &str) -> Diag<'diag> { struct_span_code_err!(self.dcx(), span, E0594, "cannot assign to {}", desc) }

也就是说,如今凡是“给不可变位置赋值”的非法操作,都会被报告为E0594cannot assign to ...——包括向被闭包捕获的不可变变量赋值这一历史 E0595 场景。这解释了为什么E0595.md中错误示例的注解是compile_fail,E0594

另外两处 rustc 内部文档中的示例也印证了这一点:mir/syntax.rs 与 ty/closure.rs 中讲解闭包捕获/不可变赋值语义时使用的可执行示例,其期望错误码均为compile_fail,E0594,与本文档的迁移说明一致。

底层实现:mutability 诊断如何走到 E0594

E0595 对应语义在编译器中的“继承者”,本质上是 borrowck 对 MIR 中不可变 place 的赋值检查。其处理入口位于 mutability_errors.rs,核心函数是report_mutability_error

从源码结构可以还原如下链路:

  • borrowck 检测到对某个PlaceRef的写入或可变借用不合法时,按AccessKind分支处理:
    • AccessKind::Mutate(纯赋值场景):调用上面的cannot_assign,产出E0594,并在分支中把动词抽象为assign/written to
    • AccessKind::MutableBorrow(可变借用场景):调用cannot_borrow_path_as_mutable_because,产出E0596cannot borrow {} as mutable
  • 当不可变原因与被闭包捕获的局部变量相关时,诊断还会尝试给出建议性标注(span_suggestion/span_label),例如提示“consider changing this to be mutable”,即建议在绑定处补上mut

一个值得注意的实现细节:当同一不可变绑定被多次尝试可变借用时(例如一个不可变let绑定被多个&mut请求命中),borrowck 会做错误合并——get_buffered_mut_error会把第二次及以后的尝试合并进以绑定声明位置为主 span 的单个诊断,并标注not mutable,避免刷屏(见 mutability_errors.rs)。这体现了 rustc 在“同一个根因(绑定缺少mut)下收敛诊断数量”的设计思路。

正确修复:变量可变性归位

回到文档中的修复建议。核心原则是:谁拥有这份数据,谁就决定它是否可写

// 错误写法:x 不可变,却要被子闭包写入 let x = 3; let mut c = || { x += 1 }; // E0594: cannot assign to `x`, as it is not declared as mutable
// 修复:让绑定可变 let mut x = 3; let mut c = || { x += 1 };

补充说明:

  • 这里对c本身加不加mut只决定闭包变量是否可被重新赋值,不影响闭包体内部修改捕获变量的合法性;上述场景需要的是x可变;
  • 若闭包只是在读取x,则x无需mut,这也是闭包最常见、最安全的使用形态;
  • 可变借用(&mut)与直接赋值走的是不同诊断路径:前者对应 E0596,后者对应 E0594。

邻近错误码家族:避免概念混淆

E0595 退役之后,围绕“不可变性”的常用错误码容易混淆,这里结合仓库中的 error_codes 目录 与 borrowck 源码作一区分:

错误码语义触发形态当前状态
E0594cannot assign to ...,对不可变位置赋值给未声明为mut的变量/字段/闭包捕获值赋值,如ss.earth = 2仍在使用,见 borrowck_errors.rs 与 E0594.md
E0595闭包修改不可变的被捕获变量let x = 3; let c = \|\| { x += 1 };已退役,场景移交 E0594
E0596cannot borrow ... as mutable,对不可变位置取可变引用&mut x其中x未加mut,包括经闭包捕获后取可变借用仍在使用,见 borrowck_errors.rs
E0384cannot assign ... twice to immutable variable/ 对不可变参数重复赋值同一不可变绑定被二次赋值仍在使用,见 borrowck_errors.rs 中cannot_reassign_immutable
E0506赋值给已被借用(borrowed)的位置借用未结束时对被借值赋值仍在使用,见cannot_assign_to_borrowed

粗看它们都是“赋值出错”,但边界清晰:E0384 针对在同一作用域内第二次赋值(与是否经由闭包无关);E0506 针对存在活跃借用;E0594 针对位置本身不可变(含通过闭包捕获引用到不可变绑定);E0596 则负责可变借用那一支。

在测试与文档体系中的佐证

作为“已退役”状态的可验证证据:

  • 在 tests/ui/error-codes/ 目录中只存在E0594.rsE0596.rsE0597.rsE0599.rs等当前活跃错误码的用例,没有E0595.rs,说明 rustc 测试套件已不再针对 E0595 单独断言;
  • compiler/源码树中E0595仅出现于本错误码文档自身以及 E0594.md 的可执行示例注解中,不存在任何struct_span_code_err!仍以 E0595 作为错误码的发出点。

rustc 在整理错误码时,会以“保留文档条目 + 标注退役”的方式处理那些被合并/重构掉的代码,而非直接删除条目——这正是 E0595.md 的存在意义:当你在历史资料、旧版本教程或存量代码评审中遇到E0595时,可以立刻定位到它今天的等价物 E0594,并理解闭包捕获不变量的修复路径(为被写入的捕获变量补mut),不会因错误码改名而困惑。

小结

E0595 是 rustc 早期用于报告“闭包修改被捕获的不可变变量”的错误码,如今已不再独立发出,该场景统一由 E0594cannot assign to ...承担(经rustc_borrowckcannot_assign诊断构造器发出,处理入口在 mutability_errors.rs 的report_mutability_error)。遇到此类问题时,正确的修复是让被闭包写入的变量以mut声明;若需要的是可变借用,则留意 E0596。理解这段演进,能帮助你在面对新版编译器输出与旧文档不一致时快速对齐语义,也更清楚闭包“捕获即引用、可变性归属绑定”的 Rust 设计原则。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询