编译器错误 E0764:常量中使用可变引用
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
导读
本文围绕 Rust 编译器(rustc)错误码E0764(在常量/静态项中使用了可变引用&mut)展开,结合当前仓库中该错误的定义文档、诊断实现与测试用例,完整讲解错误的触发场景、背后的设计动机(防止“全局可变常量”)、修复方案,以及const fn为何可以安全使用&mut的底层原理。读完本文,你将能准确诊断此类编译错误,并掌握在const、static与const fn之间正确放置&mut引用的边界。
一、错误速览:E0764 是什么
E0764 的官方定义位于 compiler/rustc_error_codes/src/error_codes/E0764.md,其核心描述为:
A mutable reference was used in a constant.(在常量中使用了可变引用。)
也就是说:在const或static的求值表达式中,如果最终值里包含&mut可变引用,编译器就会报出 E0764。
二、最小复现示例
文档给出的最简错误示例:
fn main() { const OH_NO: &'static mut usize = &mut 1; // error! }这里把&mut 1(对临时值1的可变借用)赋给常量OH_NO。编译时会得到类似如下的诊断:
error[E0764]: mutable borrows of temporaries that have their lifetime extended until the end of the program are not allowed --> src/main.rs:2:34 | 2 | const OH_NO: &'static mut usize = &mut 1; // error! | ^^^^ this mutable borrow refers to such a temporary | = note: temporaries in constants and statics can have their lifetime extended until the end of the program = note: to avoid accidentally creating global mutable state, such temporaries must be immutable三、为什么会禁止:设计动机
文档明确指出,这一限制的存在是为了防止在常量/静态项的最终值中出现可变引用。原因很直接:
如果你有一个
&mut i32类型的常量,就可以通过该引用修改其指向的值,从而让常量实质上变成可变的——即“全局可变状态”。
常量在 Rust 语义中意味着“编译期可确定、不可变、可内联到使用点”,一旦允许&mut泄漏进最终值,任何代码都能借由此引用改写“常量”,破坏这一保证。
值得一提的是,文档也给出了设计取舍的说明:
未来或许存在更细粒度的方案(只要
&mut不“泄漏”到最终值就允许使用),但现阶段选择了更保守的策略:一律禁止。
这种保守策略换来的直接收益是:规则简单、可预测,而const fn内部借用检查器足以防止新的可变引用逃逸到返回值中(详见第五节)。
源码中的诊断实现
当前仓库中 E0764 对应MutableBorrowEscaping诊断结构体,位于 compiler/rustc_const_eval/src/diagnostics.rs:
#[derive(Diagnostic)] #[diag("mutable borrows of temporaries that have their lifetime extended until the end of the program are not allowed", code = E0764)] #[note( "temporaries in constants and statics can have their lifetime extended until the end of the program" )] #[note("to avoid accidentally creating global mutable state, such temporaries must be immutable")] #[help( "if you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut`" )] pub(crate) struct MutableBorrowEscaping { #[primary_span] #[label("this mutable borrow refers to such a temporary")] pub span: Span, pub kind: ConstContext, }可见编译器不仅报错,还会附带两条note与一条help,直接提示修复方向(内嵌可变static或static mut),这正是我们下一节要讲的解决方案。
四、如何修复:三条可靠路径
路径 1:改用不可变引用&T
如果语义上只需要只读访问,直接把&mut换成&即可:
const OK: &'static usize = &1; // ok!路径 2:把“可变”挪进static/static mut
如果确实需要全局可变状态,文档与诊断信息共同给出的建议是使用显式的static mut或具有内部可变性的static:
// 显式的 static mut,编译器允许通过它建立 &mut static mut BUFFER: i32 = 42; const fn ptr_to_buffer() -> Option<&'static mut i32> { unsafe { Some(&mut *std::ptr::addr_of_mut!(BUFFER)) } } const MUT_TO_BUFFER: Option<&mut i32> = ptr_to_buffer(); // ok!该例取自 tests/ui/consts/const-mut-refs/mut_ref_in_final.rs:因为存在显式的static mut声明,可变状态有明确归属,因此允许常量/静态引用它,测试注释明确写道 “Allowed, because there is an explicit static mut.”。同一文件中还有对应的合法示例:
static mut MUT_ARRAY: &mut [u8] = &mut [42]; static MUTEX: std::sync::Mutex<&mut [u8]> = std::sync::Mutex::new(unsafe { &mut *MUT_ARRAY }); // ok!(见 mut_ref_in_final.rs,“Just statics pointing to mutable statics, nothing fundamentally wrong with this.”)
路径 3:改用具有内部可变性的容器(如Mutex/UnsafeCell)
诊断的help提示 “interior mutablestatic”,即通过static Mutex<T>、static AtomicU32这类类型把可变性封装在安全接口之后,而非让&mut直接暴露在常量值中。
注意:
static mut本身在现代 Rust 中被视为不鼓励的用法(依赖static_mut_refs等 lint),示例代码中均使用了unsafe,这是 Rust 当前语义下的必然要求;本文仅作技术说明,不构成对生产代码的建议。
五、const fn中为什么可以用&mut
文档强调了一个看似矛盾、实则自洽的事实:可变引用不能出现在const/static的最终值里,但完全可以出现在const fn的函数体内。
const fn foo(x: usize) -> usize { let mut y = 1; let z = &mut y; *z += x; y } fn main() { const FOO: usize = foo(10); // ok! }原因有两层:
- 作用域封闭:
const fn内部的局部变量(如y)生命周期随函数调用结束而结束,&mut y无法逃逸到函数返回值之外;借用检查器保证了const fn不会返回新建的可变引用。 - 返回值是具体值:上述
foo返回的是usize(y的值),&mut只是计算过程中的临时工具,最终值里根本不存在引用,自然不构成 E0764。
补充说明:文档同时提醒“不可以在const/static中调用普通函数”,因为普通函数可能有副作用、无法保证编译期求值;而const fn可在编译期求值,因此能在常量初始化中放心调用。
六、更精细的边界:临时值 vs. 显式static mut
E0764 的判定并非“一切&mut进常量都禁止”,而是针对“生命周期被延长到程序结束的临时值”。这一点在测试中有非常清晰的刻画:
- 禁止:
const B: *mut i32 = &mut 4;(对临时值4的可变借用,见 mut_ref_in_final.rs); - 允许:
const B2: Option<&mut i32> = None;(没有实际的可变分配,见同文件 L19); - 允许:
const C: *const i32 = &{ let mut x = 42; x += 3; x };(块先把值 move 出去再取引用,见同文件 L35-L39)。
同一思路在 tests/ui/consts/issue-17718-const-bad-values.rs 也有体现:
const C1: &'static mut [usize] = &mut []; //~ ERROR: mutable borrows of temporaries static mut S: i32 = 3; const C2: &'static mut i32 = unsafe { &mut S }; // ok:指向显式 static mut另外,static与const一视同仁,同样受此限制。测试 mut_ref_in_final.rs 里,static RAW_MUT_CAST_S、static RAW_MUT_COERCE_S、const RAW_MUT_CAST_C、const RAW_MUT_COERCE_C四者全部报出 “mutable borrows of temporaries” 错误——无论&mut被强转为*mut/*const还是藏在结构体字段中,只要指向被延长生命周期的临时值,都会被拦截。
源码侧如何判定
该判定的底层实现位于 compiler/rustc_const_eval/src/check_consts/ops.rs:
/// This op is for `&mut` borrows in the trailing expression of a constant /// which uses the "enclosing scopes rule" to leak its locals into anonymous /// static or const items. pub(crate) struct EscapingMutBorrow;EscapingMutBorrow实现了NonConstOptrait,其status_in_item直接返回Status::Forbidden(恒禁止),并在build_error中创建MutableBorrowEscaping(即 E0764)诊断。这里的 “enclosing scopes rule” 指的正是:常量的尾表达式会把局部临时值“泄漏”成匿名静态/常量项,使其生命周期延长至程序结束——这正是 E0764 要堵住的洞口。
七、常见疑问速查
| 场景 | 是否触发 E0764 | 说明 |
|---|---|---|
const X: &mut i32 = &mut 1; | 是 | 可变借用泄漏进最终值 |
static X: &mut i32 = &mut 1; | 是 | static同样受限 |
const fn f() { let z = &mut y; ... } | 否 | 借用不逃逸、最终值不含引用 |
const X: &i32 = &1; | 否 | 不可变引用允许 |
static mut S: i32 = 1; const X: &mut i32 = unsafe { &mut S }; | 否 | 显式static mut提供归属 |
const X: Option<&mut i32> = None; | 否 | 最终值不含实际可变分配 |
普通函数调用出现在const初始化中 | 否(但报其他错) | 常量中不能调用非常量函数 |
八、小结
E0764 是 Rust 为保证常量语义纯净性而设的“防火墙”:const/static的最终值中不允许出现&mut。遇到该错误时,按以下顺序排查:
- 确认是否真的需要可变性——若只需只读,改用
&T; - 若需要全局可变状态,改用显式
static mut或Mutex/AtomicU32等内部可变类型; - 若只是计算过程中的临时借用,把逻辑封装进
const fn,让借用检查器替你兜底。
结合 E0764 定义文档、诊断实现、判定逻辑 以及 约束测试用例,你可以完整追溯该错误的“为什么”与“怎么办”,并把它推广到任何涉及const/static与引用交织的代码审查中。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考