变量与可变性:从let到Shadowing的完整认知
大家好,我是你们的Rust引路人。今天我们来聊聊Rust里最基础、但也最容易让人困惑的概念——变量与可变性。
说实话,我第一次接触Rust时,就被它的变量系统震撼到了。别的语言里,变量就是变量,想改就改。但在Rust这里,事情没那么简单。它把「变量」和「可变性」拆成了两个独立的概念。嗯,这背后有它的道理。
let声明:默认不可变
在Rust中,声明一个变量用let关键字。但请注意,默认情况下,这个变量是不可变的。
let x = 5;
x = 6; // 编译错误!不能对不可变变量二次赋值
为什么会这样?说白了,Rust的设计哲学就是「默认安全」。不可变意味着数据一旦绑定,就不会被意外修改。我在项目中遇到过好几次,因为误改了不该改的变量导致线上bug。Rust这种设计,其实是在帮你提前规避这类问题。
核心认知:Rust中的「变量」默认是只读绑定。你声明了一个名字,把它绑定到一个值上,这个绑定关系默认不可更改。
mut:显式声明可变性
当然,现实世界需要可变性。比如计数器、累加器、循环索引……这时候就需要mut关键字出场了。
let mut count = 0;
count += 1; // 没问题
count += 2; // 没问题
println!("count = {}", count); // 输出: count = 3
我个人习惯是:能用不可变就用不可变,实在需要可变再加mut。你想想看,代码里每多一个mut,就多一分被意外修改的风险。Rust让你显式地写出来,其实就是让你自己确认:「嗯,这个变量确实需要变,我负责。」
小技巧:在函数参数中,如果你需要修改传入的值,记得在参数前加mut。比如 fn add_one(x: &mut i32)。我曾经因为忘了加mut,卡了半小时的编译错误。
const:编译期常量
接下来是const。它和let有本质区别。
| 特性 | let | const |
|---|---|---|
| 可变性 | 默认不可变,可加mut | 永远不可变 |
| 求值时机 | 运行时 | 编译时 |
| 类型标注 | 可省略(类型推断) | 必须显式标注 |
| 作用域 | 块级作用域 | 全局/块级作用域 |
| 命名规范 | snake_case | SCREAMING_SNAKE_CASE |
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.1415926535;
注意,const的值必须在编译期就能确定。你不能写const X: i32 = rand::random();,因为随机数在编译期算不出来。
避坑指南:我曾经在代码里把配置项写成const,结果发现运行时改不了配置。后来才意识到,const是编译期硬编码的。如果你需要运行时配置,请用let + mut,或者用lazy_static这类库。
变量遮蔽(Shadowing)
这个特性,说实话,我第一次看到时觉得「这不就是重新声明吗?」但用久了才发现,它真的很巧妙。
变量遮蔽允许你在同一个作用域内,用let重新声明一个同名变量。新变量会「遮蔽」旧变量,旧变量在遮蔽后无法访问。
let x = 5;
let x = x + 1; // 遮蔽,x现在是6
let x = x * 2; // 再次遮蔽,x现在是12
println!("x = {}", x); // 输出: x = 12
你想想看,这和mut有什么区别?
- mut:改变同一个内存位置的值,类型不能变
- Shadowing:创建新的绑定,旧绑定被隐藏,类型可以变
举个例子,你从用户输入读到一个字符串,想把它转成数字:
let input = "42";
let input: i32 = input.parse().unwrap(); // 类型从&str变成了i32
// 现在input是数字,不再是字符串
如果用mut,你做不到类型转换。但Shadowing可以。我个人习惯在需要「转换类型但保留变量名」的场景下使用它。比如从字符串解析到数字,或者从宽类型转换到窄类型。
注意:Shadowing不是修改,是重新绑定。旧变量仍然存在,只是被遮蔽了。如果你在嵌套作用域中遮蔽,离开作用域后旧变量会恢复。
let x = 10;
{
let x = 20; // 遮蔽外部x
println!("内部: {}", x); // 20
}
println!("外部: {}", x); // 10,外部x恢复
知识体系总览
下面这张图,是我梳理的变量与可变性的核心逻辑。你看完应该能一目了然。
使用场景总结
说了这么多,到底什么时候用哪个?我根据自己的经验,给你一个参考:
- let(不可变):绝大多数场景。配置值、计算结果、函数返回值……能用不可变就用不可变。
- let mut:循环计数器、累加器、状态机状态、需要频繁更新的数据。
- const:数学常量、魔法数字、全局配置(编译期确定的值)。
- Shadowing:类型转换、临时变量复用、作用域内临时遮蔽。
我的建议:刚开始学Rust时,你可能会觉得「怎么这么麻烦,一个变量搞这么多花样」。但用久了你会发现,这种设计让你在写代码时就能发现很多潜在问题。我曾经在一个大型项目中,因为Rust的不可变默认,避免了至少3次数据竞争bug。相信我,这个设计值得你花时间理解。
好了,变量与可变性就聊到这里。记住一句话:默认不可变,显式可变,常量编译期,遮蔽可换型。把这四个概念理清楚,Rust的类型系统你就迈出了坚实的第一步。