mut 关键字如何改变变量语义?
解读
在国内 Rust 社招/校招面试中,这道题几乎必问。面试官想确认两点:
- 你是否真正理解「默认不可变」这一 Rust 核心哲学;
- 你是否能区分「变量绑定可变性」与「内存地址可写性」这两个不同层面的概念。
只回答“mut 让变量可变”会被追问“可变到何种程度”“是否影响所有权”“与内部可变性怎么区分”,因此必须层层展开。
知识点
- 默认不可变(Immutable by default)
let x = 1; 之后,x 不可再赋值,也不可获取 &mut x。 - mut 关键字位置与语义
- let mut x:绑定可变,允许重新赋值与获取唯一可变引用。
- &mut T:可变引用,遵循“独占借用规则”(同一时间只能存在一个 &mut T)。
- 不影响所有权转移
mut 只改“绑定可变性”,不改变 move/copy 语义。 - 与内部可变性(Interior Mutability)区分
Cell、RefCell、Mutex 等允许在“不可变绑定”背后修改数据,属于编译器特殊批准的“作弊”手段。 - 对 FFI 与 unsafe 的影响
裸指针 *mut T 与 *const T 的差异,unsafe 块内解引用时必须匹配,否则触发 UB。 - 并发语义
数据竞争检测依赖 &mut T 的独占性,mut 是“无锁并发安全”的基石。
答案
在 Rust 中,变量绑定默认是不可变的;一旦使用 let mut 声明,编译器即将该绑定的语义从“只读”升级为“可读可写”,具体表现为:
- 允许对该绑定重新赋值:x = new_value;
- 允许获取 &mut T 唯一可变引用,进而修改目标内存;
- 由于 &mut T 遵循独占借用规则,同一作用域内不能同时存在其他 &T 或 &mut T,从而在编译期消除数据竞争;
- mut 不改变所有权转移规则,move 之后原绑定仍失效;
- 对于结构体字段,mut 只向下传播一层:let mut foo 并不能自动让 foo.x 可变,必须同时声明 foo.x 为 mut 类型或使用内部可变性容器。
一句话总结:mut 关键字把“只读绑定”升级为“独占读写绑定”,是 Rust 内存安全与并发无锁模型的闸门。
拓展思考
- 面试常追问:若把 &mut T 强制转成 *mut T 再转成 const T,再跨线程读取,是否安全?
答:不安全,独占性被手动破坏,构成 UB;需用 Atomic 或 Mutex 重新获得同步。 - 与 C++ 的对比:C++ 的“非 const 变量”默认可随时别名,Rust 的 mut 配合借用检查器在编译期拒绝别名,因此无需 volatile 也能做零成本并发抽象。
- 实际工程踩坑:在 Tokio 异步代码里,把 !Unpin 的 Future 放进 let mut 之后忘记 pin,会导致编译器提示“future moved after await”;此时 mut 只是必要条件,还需 Pin<Box> 或 stack pinning 才能通过。
- 进阶话题:mut 关键字在模式匹配中的位置也有讲究,match 时绑定新变量默认不可变,需写 ref mut 才能拿到可变引用,面试可主动提及以展示深度。