3. 变量与可变性:变量声明、可变与不可变、常量、变量遮蔽(Shadowing)、作用域

好,咱们今天来聊聊 Rust 里最基础,但也最容易踩坑的一个话题——变量。

很多从 Python、JavaScript 转过来的同学,第一次写 Rust 时都会懵:

“为什么我改个变量的值,编译器还报错?”

嗯,这恰恰是 Rust 的用心之处。它逼着你从一开始就想清楚:这块数据,到底要不要变?

3.1 变量声明:let 是起点

在 Rust 里,声明一个变量用 let 关键字。语法很简单:

let x = 5;
println!("x 的值是:{}", x);

就这么一行,x 就被绑定到了值 5 上。注意我说的是“绑定”,不是“赋值”。

我个人习惯把 let 理解为“给值起个名字”。这个名字和值之间,是一种绑定关系。

小提示:Rust 默认变量是不可变的。也就是说,上面这行代码之后,你不能写 x = 6,编译器会直接报错。

3.2 可变与不可变:mut 是关键

那如果我真的想改变一个变量的值呢?加个 mut 就行:

let mut y = 10;
y = 20;
println!("y 现在是:{}", y);

看到没?mut 就是“mutable”的缩写。它告诉编译器:这个变量我后面可能会改,你别拦我。

我在项目中遇到过这样一个场景:写一个计数器,需要不断累加。一开始忘了加 mut,编译器直接甩我一脸错误。当时我还觉得 Rust 真啰嗦,后来想想,这其实是在保护我——万一哪个变量不该变却被改了,bug 就藏进去了。

避坑指南:我曾经在团队代码 review 时发现,有人为了省事,把所有变量都声明成 let mut。这其实是个坏习惯。你想想看,如果一个变量从头到尾只赋值一次,那它就不该是可变的。保持不可变,代码更容易推理,也更容易并发。

3.3 常量:const 的硬核之处

常量和不可变变量有点像,但更“硬”。用 const 声明:

const MAX_POINTS: u32 = 100_000;

注意几个区别:

  • 常量必须显式标注类型(比如这里的 u32
  • 常量只能被赋值为常量表达式,不能是运行时计算的结果
  • 常量在整个程序生命周期内都有效,不局限于某个作用域

说白了,常量就是“写死在代码里的值”。我一般用它来定义配置项、阈值、数学常数等。

重点:常量与不可变变量的最大区别在于——常量可以在任何作用域中声明,包括全局作用域。而 let 声明的变量只能在函数或代码块内部使用。

3.4 变量遮蔽:Shadowing 的妙用

这个特性,我第一次看到时觉得挺奇怪的。它允许你用同一个变量名,声明一个新的变量,把旧的“遮住”:

let x = 5;
let x = x + 1;
let x = x * 2;
println!("x 的值是:{}", x); // 输出 12

这里其实发生了三次绑定。每次 let x = ... 都创建了一个新的变量 x,它遮蔽了之前的那个。旧的 x 在遮蔽之后就无法访问了。

你可能会问:这和 let mut x 有什么区别?

区别大了:

  • let mut 是改变同一个变量的值,类型不能变
  • Shadowing 是创建新变量,可以改变类型

举个例子:

let spaces = "   ";
let spaces = spaces.len(); // 类型从 &str 变成了 usize

这在 let mut 下是做不到的。我个人习惯在需要“转换”变量类型或含义时使用 shadowing,比如从字符串解析成数字,或者从宽类型转为窄类型。

小技巧:Shadowing 还能帮你避免起一堆乱七八糟的中间变量名。比如 temp1temp2 这种,直接用同一个名字遮蔽掉,代码更干净。

3.5 作用域:变量活多久?

Rust 的作用域规则很简单:变量从声明开始,到所在代码块结束为止

{
    let a = 10;
    println!("a 在内部:{}", a);
} // a 在这里被销毁
// println!("a 在外部:{}", a); // 这行会报错

作用域决定了变量的生命周期。一旦出了花括号,变量就被释放了。

我记得刚开始学 Rust 时,经常搞不清楚“为什么这个变量还能用?”或者“为什么那个变量已经没了?”后来我养成一个习惯:看到花括号,就想象一个边界。变量就像住在房间里的人,出了门就不属于你了。

核心原则:作用域嵌套时,内部可以访问外部的变量,但外部不能访问内部的。这和你住的房子一样——你可以去客厅,但客厅的人不能随便进你卧室。

3.6 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

变量与可变性 知识体系 变量声明 (let) 不可变变量 let x = 5; // 默认 可变变量 let mut x = 5; 常量 const MAX: u32 = 100; 变量遮蔽 (Shadowing) let x = 5; let x = x + 1; 可改变类型,创建新绑定 作用域 { let a = 10; } // a 失效 变量生命周期由花括号决定 核心原则:默认不可变,按需加 mut,常量用 const

3.7 本章小结

来,咱们捋一捋今天讲的内容:

  • 变量声明let,默认不可变
  • 想可变就加 mut,但别滥用
  • 常量const,必须标注类型,全局有效
  • 变量遮蔽可以重用变量名,甚至改变类型
  • 作用域决定了变量的生死,花括号就是边界

这些概念看似简单,但它们是 Rust 所有权系统的基石。你想想看,如果连变量能不能变都搞不清楚,后面学引用、借用、生命周期时,肯定会一头雾水。

我个人建议:从现在开始,写 Rust 代码时多问自己一句——“这个变量真的需要变吗?”养成习惯后,你会发现代码质量提升不少。