在 Rust 里,变量默认是不可变的,这是它保证内存安全的第一道关卡。当你确实需要修改一个值时,主要有两条路:用 let mut 声明一个可变绑定,或者用 let 重新绑定(变量遮蔽)给同一个名字赋新值。两种方式都能让“值发生变化”,但语义和适用场景完全不同。很多从 C、C++ 转过来的开发者,一开始会因为忘了写 mut 被编译器拦下来。本文用最小示例讲清两种写法的差异、各自的边界,以及编译报错时怎么修,看完你就能按场景选对写法。

先看结论
先给你一张选型表,省得在两种写法之间纠结:
| 你想做的事 | 推荐写法 | 原因 |
|---|---|---|
| 同一个变量在作用域内反复被修改 | let mut |
语义明确,编译器允许原地修改 |
| 基于旧值算出新值并换个类型 | 变量遮蔽 let x = x + 1 |
可以顺手换类型,更安全 |
| 只是一次性初始化 | 默认不可变 let |
不变就不写 mut,符合 Rust 习惯 |
一句话记住:要“反复改”,用 mut;要“基于旧值换新值”,用遮蔽;都不要就保持默认不可变,这是 Rust 推荐的默认姿态。
一、let mut:声明可变绑定
Rust 的变量绑定默认不可变,想让它能被重新赋值,必须在 let 后面加 mut(mutable 的缩写)。这是最常见、最直接的可变写法。
fn main() {
let mut count = 0;
count = 1;
println!("count = {}", count);
}
上面这段先声明了一个可变的 count,初始为 0,随后把它改成 1 再打印。预期结果(未在本机执行):程序编译运行后输出 count = 1,说明 count 在被 mut 声明后可以被重新赋值。如果你还不熟悉 Rust 的变量与所有权基础,可以先过一遍 Rust 入门教程 的前几章再回来读。
需要安装的运行时很简单:用 rustup 装好工具链后,新建项目用 cargo new demo,运行用 cargo run,检查改动用 cargo build。从边界上说,mut 只作用于当前绑定所在的作用域,离开作用域后绑定就失效,这和 C、C++ 里的局部变量作用域一致。性能上,mut 是零成本抽象,编译后和不加 mut 没有运行时差别,所以放心用。
二、变量遮蔽:用 let 重新绑定同名变量
第二种让“值变化”的方式不是修改原绑定,而是用同一个名字再声明一次,新绑定会“遮蔽”旧绑定。这种方式的特点是可以顺手换类型。
fn main() {
let x = 5;
let x = x + 1; // 同名重新绑定,基于旧值算出新值
println!("x = {}", x);
}
这段里第一个 x 是 5,第二个 x 基于它算出 6 并遮蔽了前者。预期结果(未在本机执行):程序运行后输出 x = 6。注意遮蔽后旧 x 在后续代码里不可再访问,不存在“两个 x 同时存在”的情况。
这种方式在 C 语言里没有对应物,熟悉 C 语言教程 的开发者可能会觉得别扭,但它很适合“先读入字符串、再解析成数字”这种类型转换场景:第一次 let s = "42" 是字符串,第二次 let s = s.parse::<i32>() 就变成了整数,名字不变却换了类型。如果你从 C 或 C++ 转来,理解这点的关键是记住 Rust 的遮蔽不是修改原变量,而是每次 let 都生成一个全新的绑定;调试时若发现旧值还在作用域里被引用,优先检查是不是遮蔽点写错了位置,而不是怀疑编译器。相比之下,C++ 教程 里类型一旦确定就难以在同一名字上变更,这也是 Rust 设计上更强调显式的原因。
三、两种方式的横向对比与选型
| 维度 | let mut |
变量遮蔽 |
|---|---|---|
| 是否修改变量本身 | 是,原地修改 | 否,生成新绑定 |
| 能否换个类型 | 否,类型固定 | 能,可换类型 |
| 旧值是否还能访问 | 能 | 被遮蔽后不能 |
| 适用场景 | 循环计数、状态累加 | 读入后转换、计算中间值 |
选型建议:当你需要在循环里不断累加、或在函数内多次更新某个状态时,用 let mut 最自然;当你只是基于旧值算出另一个值、甚至要换个类型时,用变量遮蔽更干净,也避免了把变量设成可变后意外被改。两者都不是“更好”,而是对应不同的意图。

四、踩坑清单
下面这几条是新手最容易卡住的点,每条都给了现象、原因和修复办法。
- 现象:写了
let x = 1; x = 2;,编译器报错 cannot assign twice to immutable variable。
原因:Rust 默认绑定不可变,没写 mut 就不能二次赋值。
修复:如果确实要反复改,改成let mut x = 1; x = 2;。
- 现象:想用 mut 又想换类型,结果编译失败。
原因:mut 只让值可变,类型仍是第一次声明时定死的。
修复:改用变量遮蔽let x = ...; let x = 新类型的值;,遮蔽允许换类型。
- 现象:遮蔽之后,旧值似乎“还能用”,逻辑乱了。
原因:遮蔽只是名字复用,旧绑定在遮蔽点之后就不可访问,误以为还能用是理解偏差。
修复:明确每个同名 let 的生效范围,必要时换不同名字避免混淆。
- 现象:在 C/C++ 习惯里顺手写
mut却用在非绑定位置,直接语法错。
原因:Rust 的 mut 只出现在 let 后面或引用类型里,不能像 C++ 的 mutable 那样到处用。
修复:确认自己是在声明可变绑定,而不是在套用 C++ 的 mutable 关键字语义。

总结
Rust 定义可变变量就两件事:反复改同一值用 let mut,基于旧值换新值(可换类型)用变量遮蔽,其余情况保持默认不可变。
要点带走:
- 默认不可变,要改才加 mut,这是 Rust 的安全默认;
- mut 是零成本抽象,不影响运行时性能;
- 变量遮蔽能换类型,适合读取后转换的场景;
- 忘了写 mut 的报错信息很明确,补上即可。
下一步如果你在写循环计数或状态机,建议优先用 let mut;遇到“读入字符串再转数字”这类转换,用遮蔽最顺手。
延伸学习
想把 Rust 的基础打牢,可以按这个顺序来:
- 那篇 Rust 为什么依然流行 从设计和安全性角度讲了它值得学的理由,适合建立整体认知;
- Go 与 Rust 全面对比 能帮你理解两门系统级语言在可变性与内存模型上的取舍,做技术选型时有参考。
常见问题
Q:为什么 Rust 变量默认不可变?
A:不可变是 Rust 保证内存安全和并发安全的第一道关卡,它让编译器能在编译期就拦下大量“意外修改”的 bug。需要改时显式写 mut,等于主动声明这里会变化,代价只是多敲几个字母。
Q:let mut 和变量遮蔽能混用吗?
A:可以。常见做法是先用 let mut 声明一个会变化的状态,在个别步骤用 let x = x + 1 这种遮蔽顺手换类型。只要注意遮蔽后旧绑定不再可访问,逻辑上不会冲突。
Q:Rust 的 mut 和 C++ 的 mutable 是一回事吗?
A:不是。Rust 的 mut 用在 let 后面,表示这个绑定可以被重新赋值;C++ 的 mutable 用在类成员上,表示即使在 const 对象里该成员也能改。两者语义不同,不能套用。

TRAE-AI编程



