3. 变量与可变性:变量声明、可变与不可变、常量、变量遮蔽(Shadowing)、作用域
好,咱们今天来聊聊 Rust 里最基础,但也最容易踩坑的一个话题——变量。
很多从 Python、JavaScript 转过来的同学,第一次写 Rust 时都会懵:
“为什么我改个变量的值,编译器还报错?”
嗯,这恰恰是 Rust 的用心之处。它逼着你从一开始就想清楚:这块数据,到底要不要变?
3.1 变量声明:let 是起点
在 Rust 里,声明一个变量用 let 关键字。语法很简单:
let x = 5;
println!("x 的值是:{}", x);
就这么一行,x 就被绑定到了值 5 上。注意我说的是“绑定”,不是“赋值”。
我个人习惯把 let 理解为“给值起个名字”。这个名字和值之间,是一种绑定关系。
x = 6,编译器会直接报错。
3.2 可变与不可变:mut 是关键
那如果我真的想改变一个变量的值呢?加个 mut 就行:
let mut y = 10;
y = 20;
println!("y 现在是:{}", y);
看到没?mut 就是“mutable”的缩写。它告诉编译器:这个变量我后面可能会改,你别拦我。
我在项目中遇到过这样一个场景:写一个计数器,需要不断累加。一开始忘了加 mut,编译器直接甩我一脸错误。当时我还觉得 Rust 真啰嗦,后来想想,这其实是在保护我——万一哪个变量不该变却被改了,bug 就藏进去了。
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,比如从字符串解析成数字,或者从宽类型转为窄类型。
temp1、temp2 这种,直接用同一个名字遮蔽掉,代码更干净。
3.5 作用域:变量活多久?
Rust 的作用域规则很简单:变量从声明开始,到所在代码块结束为止。
{
let a = 10;
println!("a 在内部:{}", a);
} // a 在这里被销毁
// println!("a 在外部:{}", a); // 这行会报错
作用域决定了变量的生命周期。一旦出了花括号,变量就被释放了。
我记得刚开始学 Rust 时,经常搞不清楚“为什么这个变量还能用?”或者“为什么那个变量已经没了?”后来我养成一个习惯:看到花括号,就想象一个边界。变量就像住在房间里的人,出了门就不属于你了。
3.6 知识体系总览
下面这张图,帮你把本章的核心逻辑串起来:
3.7 本章小结
来,咱们捋一捋今天讲的内容:
- 变量声明用
let,默认不可变 - 想可变就加
mut,但别滥用 - 常量用
const,必须标注类型,全局有效 - 变量遮蔽可以重用变量名,甚至改变类型
- 作用域决定了变量的生死,花括号就是边界
这些概念看似简单,但它们是 Rust 所有权系统的基石。你想想看,如果连变量能不能变都搞不清楚,后面学引用、借用、生命周期时,肯定会一头雾水。
我个人建议:从现在开始,写 Rust 代码时多问自己一句——“这个变量真的需要变吗?”养成习惯后,你会发现代码质量提升不少。