mut 关键字如何改变变量语义?

解读

在国内 Rust 社招/校招面试中,这道题几乎必问。面试官想确认两点:

  1. 你是否真正理解「默认不可变」这一 Rust 核心哲学;
  2. 你是否能区分「变量绑定可变性」与「内存地址可写性」这两个不同层面的概念。
    只回答“mut 让变量可变”会被追问“可变到何种程度”“是否影响所有权”“与内部可变性怎么区分”,因此必须层层展开。

知识点

  1. 默认不可变(Immutable by default)
    let x = 1; 之后,x 不可再赋值,也不可获取 &mut x。
  2. mut 关键字位置与语义
    • let mut x:绑定可变,允许重新赋值与获取唯一可变引用。
    • &mut T:可变引用,遵循“独占借用规则”(同一时间只能存在一个 &mut T)。
  3. 不影响所有权转移
    mut 只改“绑定可变性”,不改变 move/copy 语义。
  4. 与内部可变性(Interior Mutability)区分
    Cell、RefCell、Mutex 等允许在“不可变绑定”背后修改数据,属于编译器特殊批准的“作弊”手段。
  5. 对 FFI 与 unsafe 的影响
    裸指针 *mut T 与 *const T 的差异,unsafe 块内解引用时必须匹配,否则触发 UB。
  6. 并发语义
    数据竞争检测依赖 &mut T 的独占性,mut 是“无锁并发安全”的基石。

答案

在 Rust 中,变量绑定默认是不可变的;一旦使用 let mut 声明,编译器即将该绑定的语义从“只读”升级为“可读可写”,具体表现为:

  1. 允许对该绑定重新赋值:x = new_value;
  2. 允许获取 &mut T 唯一可变引用,进而修改目标内存;
  3. 由于 &mut T 遵循独占借用规则,同一作用域内不能同时存在其他 &T 或 &mut T,从而在编译期消除数据竞争
  4. mut 不改变所有权转移规则,move 之后原绑定仍失效;
  5. 对于结构体字段,mut 只向下传播一层:let mut foo 并不能自动让 foo.x 可变,必须同时声明 foo.x 为 mut 类型或使用内部可变性容器。

一句话总结:mut 关键字把“只读绑定”升级为“独占读写绑定”,是 Rust 内存安全与并发无锁模型的闸门

拓展思考

  1. 面试常追问:若把 &mut T 强制转成 *mut T 再转成 const T,再跨线程读取,是否安全?
    答:不安全,独占性被手动破坏,构成 UB;需用 Atomic
    或 Mutex 重新获得同步。
  2. 与 C++ 的对比:C++ 的“非 const 变量”默认可随时别名,Rust 的 mut 配合借用检查器在编译期拒绝别名,因此无需 volatile 也能做零成本并发抽象。
  3. 实际工程踩坑:在 Tokio 异步代码里,把 !Unpin 的 Future 放进 let mut 之后忘记 pin,会导致编译器提示“future moved after await”;此时 mut 只是必要条件,还需 Pin<Box> 或 stack pinning 才能通过。
  4. 进阶话题:mut 关键字在模式匹配中的位置也有讲究,match 时绑定新变量默认不可变,需写 ref mut 才能拿到可变引用,面试可主动提及以展示深度。