Rust 引用的 4 种可变性组合:let mut 与 &mut,到底谁管谁?

Rust 引用的 4 种可变性组合:let mut 与 &mut,到底谁管谁?

💡 原文中文,约5900字,阅读约需14分钟。
📝

内容提要

Rust 引用有两层可变性控制:let 后的 mut 决定指针能否改指,& 后的 mut 决定数据能否修改,组合出四种形态。文章逐一讲解各组合的读写权限与典型场景,并对照 C++ 的 const 指针和 Go 的指针设计。还指出三个陷阱:&T 并非绝对不可变;&mut 不实现 Copy,但函数传参会自动重借用;函数参数中的 mut 同样只管变量。

🔎

延伸解读

理解两层可变性的实用心法

文章强调,Rust 引用的可变性由两个独立位置控制:let 后的 mut 决定指针能否改指,& 后的 mut 决定数据能否修改。掌握这个心法后,就能不依赖编译器,快速判断任意引用代码的合法性。例如,let mut r = &x 允许改指但不能改数据,而 let r = &mut x 允许改数据但不能改指。这种拆分让 Rust 比 C++ 的 const 位置规则更直观,也避免了 Go 中无法表达只读指针的局限。

与 C++、Go 的横向对比

文章将 Rust 的四种组合与 C++、Go 对照:C++ 用 const 在 * 左右区分锁数据或锁指针,但语法易错;Go 只有类似组合 4 的形式,无法在类型层面限制指针只读或数据只读。Rust 不仅提供四种组合,还通过借用检查器保证 &mut 的排他性,避免数据竞争。这种设计让 Rust 在编译期就能捕获更多错误,而 Go 需要依赖约定和代码评审。

三个容易忽略的进阶陷阱

文章指出三个陷阱:第一,&T 并非绝对不可变,通过内部可变性类型(如 Cell)仍可修改数据;第二,&mut 不实现 Copy,但函数传参时编译器会自动重借用,导致看似能多次传递;第三,函数参数中的 mut 同样只控制变量本身,而非数据。理解这些细节能避免在泛型、闭包等场景中遇到意外的移动错误,也为学习 RefCell、Mutex 打下基础。

❓

Q&A

Rust 中 let mut 和 &mut 分别控制什么?

let 后面的 mut 控制变量本身能否重新赋值,即指针能否改指;& 后面的 mut 控制指针指向的数据能否被修改。两者独立,组合出四种可变性形态。

Rust 引用的四种可变性组合分别是什么?各自能做什么?

1. let r = &x:不能改指,不能改数据;2. let mut r = &x:能改指,不能改数据;3. let r = &mut x:不能改指,能改数据;4. let mut r = &mut x:能改指,能改数据。

为什么 let mut p = &a; *p = 10; 会编译错误?

因为 let mut p 只让变量 p 可以改指向,但 p 的类型是 &i32(共享引用),不允许通过它修改数据。要修改数据,引用必须是 &mut,即 let mut p = &mut a。

Rust 的引用可变性设计与 C++、Go 有什么不同?

C++ 用 const 相对 * 的位置区分(如 const T* const 对应 let r = &x),但语法易错;Go 没有 const 指针,只有类似组合 4 的形式,无法在类型层面表达只读指针;Rust 将两层控制拆到 let 和 & 两个位置,并用借用检查器保证 &mut 的排他性。

&T 是绝对不可变的吗?

不是。&T 是共享引用,当 T 具有内部可变性时(如 Cell),通过 &T 依然可以修改数据。更准确的理解是:&T 保证不会通过该引用以普通方式修改数据,且可能有多人同时访问;&mut T 的核心语义是独占访问。

为什么 &mut 不是 Copy 类型,但连续传给函数却不报错?

&mut T 没有实现 Copy,因为复制独占引用会破坏独占性。但函数传参时编译器会自动重借用(reborrow),相当于把 modify(r) 改写成 modify(&mut *r),临时借出再归还,所以连续调用能通过。若重借用未触发(如泛型参数、闭包捕获),则会报 use of moved value。

函数参数中的 mut 有什么作用?

函数参数中的 mut 同样只管变量本身,类型里的 &mut 管数据。例如 fn f2(mut v: &mut Vec<i32>, other: &mut Vec<i32>) 中,mut v 允许 v 改指向(v = other),而 &mut 允许修改数据(v.push(1))。

🏷️

标签

➡️

继续阅读