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,你没法改变类型。这就是隐藏的妙处——名字不变,类型可以变

注意: 隐藏不是修改,是“换人”。原来的变量还在内存里,只是你访问不到了。如果你不小心隐藏了重要变量,可能会造成混淆。我曾经在循环里反复隐藏同一个变量,结果 debug 时把自己绕晕了……后来我规定自己:隐藏最多用两次,超过就换名字

3.3 常量(const)

常量跟不可变变量有点像,但区别很大。

const MAX_POINTS: u32 = 100_000;

几个关键点:

  • 必须显式标注类型(不能省略)
  • 值必须是编译期常量(不能是函数调用结果)
  • 命名惯例是全大写加下划线
  • 可以在任何作用域声明(包括全局)

常量跟 let 最大的不同是:常量在编译时就被内联到使用处。也就是说,你写 MAX_POINTS 的地方,编译后直接变成 100000。而 let 变量是在运行时分配的。

我在项目中常用常量来定义配置值,比如缓冲区大小、超时时间、版本号等。这样改一处,全局生效,而且编译器会帮你检查类型。

核心区别总结:
特性letconst
可变性默认不可变,可加 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,发现它还是空的——因为内层的隐藏并没有修改它。后来我改成用 mutclear() 方法,才解决问题。所以,隐藏和修改是两码事,别搞混了

3.5 知识体系图

下面这张图帮你理清本章的核心逻辑:

变量与可变性 let 声明 mut 可变性 变量隐藏 常量 const 变量作用域 let vs const 核心:默认不可变 → 按需可变 → 隐藏与常量作为补充

3.6 小结

这一章的内容其实就一句话:Rust 的变量默认是“锁着的”,你需要主动开锁(mut)或者换人(隐藏)

常量是另一种“锁”——锁得更死,但能在全局用。作用域则是变量的“生命周期”——到点就消失,干净利落。

我个人觉得,理解这些概念的关键不在于记住语法,而在于体会 Rust 的设计哲学:能静态检查的,绝不拖到运行时;能默认安全的,绝不默认灵活。你写代码时多花 5 秒钟想一下“这个变量要不要可变”,后面 debug 时就能省下 5 小时。

一句话总结: let 是声明,mut 是许可,隐藏是换人,const 是铁律,作用域是边界。把这四个搞清楚,Rust 的变量系统你就拿下了。

专注资料整理