3. 变量与可变性:let与mut关键字、变量隐藏、常量与作用域
好,咱们今天来聊聊 Rust 里最基础、但也最容易踩坑的一块——变量。
说实话,我刚从 C++ 转到 Rust 时,第一反应是:这语言怎么这么“拧巴”?变量默认不让改?还得专门写个 mut?但用久了你会发现,这恰恰是 Rust 的良心设计。
3.1 let 与 mut:默认不可变
在 Rust 里,你用 let 声明一个变量,默认就是不可变的。
let x = 5;
x = 6; // 编译错误!
我第一次看到这个报错时,心里想:凭什么?后来在项目中维护一个多线程状态机,发现某个变量被到处乱改,查 bug 查到凌晨三点。从那以后,我彻底理解了 Rust 的良苦用心——默认不可变,就是帮你减少“意外修改”的坑。
那如果确实需要改呢?加个 mut 就行:
let mut y = 5;
y = 6; // 没问题
我个人习惯是:能用不可变就用不可变。只有明确需要修改时,才加 mut。这其实是一种“契约”——告诉读代码的人:这个变量后面会变,你留意一下。
mut。编译器会帮你检查,省心。
3.2 变量隐藏(Shadowing)
这个特性很有意思。你可以用同一个变量名,重新声明一个变量——这叫“隐藏”。
let x = 5;
let x = x + 1; // 隐藏了之前的 x
let x = x * 2; // 再次隐藏
println!("{}", x); // 输出 12
注意,这里没有用 mut。每次 let x 都是创建一个全新的变量,之前的那个被“遮住”了。
为什么需要这个?我举个例子。你在处理用户输入时,先拿到一个字符串,然后想把它转成数字:
let input = "42";
let input: i32 = input.parse().unwrap(); // 隐藏,类型变了
你看,变量名还是 input,但类型从 &str 变成了 i32。如果用 mut,你没法改变类型。这就是隐藏的妙处——名字不变,类型可以变。
3.3 常量(const)
常量跟不可变变量有点像,但区别很大。
const MAX_POINTS: u32 = 100_000;
几个关键点:
- 必须显式标注类型(不能省略)
- 值必须是编译期常量(不能是函数调用结果)
- 命名惯例是全大写加下划线
- 可以在任何作用域声明(包括全局)
常量跟 let 最大的不同是:常量在编译时就被内联到使用处。也就是说,你写 MAX_POINTS 的地方,编译后直接变成 100000。而 let 变量是在运行时分配的。
我在项目中常用常量来定义配置值,比如缓冲区大小、超时时间、版本号等。这样改一处,全局生效,而且编译器会帮你检查类型。
| 特性 | let | const |
|---|---|---|
| 可变性 | 默认不可变,可加 mut | 永远不可变 |
| 类型标注 | 可省略(类型推断) | 必须标注 |
| 作用域 | 块作用域 | 块作用域或全局 |
| 编译期求值 | 否 | 是 |
| 隐藏 | 支持 | 不支持 |
3.4 变量作用域
Rust 的作用域规则很简单:变量从声明处开始,到所在花括号结束处失效。
fn main() {
let x = 5; // x 从这里开始生效
{
let y = 10; // y 从这里开始生效
println!("{} {}", x, y); // 可以访问 x 和 y
} // y 在这里失效
// println!("{}", y); // 编译错误!y 已不存在
} // x 在这里失效
嗯,这里要注意:作用域是嵌套的。内层可以访问外层变量,但外层不能访问内层。这跟大多数语言一样。
但 Rust 有个特别的地方——变量隐藏可以在不同作用域里发生:
let x = 1;
{
let x = 2; // 隐藏了外层的 x
println!("{}", x); // 2
}
println!("{}", x); // 1,外层的 x 还在
你看,内层的 x 只是暂时遮住了外层的 x,一旦离开内层作用域,外层的 x 又“复活”了。这跟 mut 完全不同——mut 是修改同一个变量,而隐藏是创建了一个新变量,只是名字相同而已。
buffer,然后在循环里又 let buffer = ... 隐藏了它。结果循环结束后,我想用外层的 buffer,发现它还是空的——因为内层的隐藏并没有修改它。后来我改成用 mut 加 clear() 方法,才解决问题。所以,隐藏和修改是两码事,别搞混了。
3.5 知识体系图
下面这张图帮你理清本章的核心逻辑:
3.6 小结
这一章的内容其实就一句话:Rust 的变量默认是“锁着的”,你需要主动开锁(mut)或者换人(隐藏)。
常量是另一种“锁”——锁得更死,但能在全局用。作用域则是变量的“生命周期”——到点就消失,干净利落。
我个人觉得,理解这些概念的关键不在于记住语法,而在于体会 Rust 的设计哲学:能静态检查的,绝不拖到运行时;能默认安全的,绝不默认灵活。你写代码时多花 5 秒钟想一下“这个变量要不要可变”,后面 debug 时就能省下 5 小时。