3. 变量与可变性:从let到static的完整认知
变量是编程语言最基础的概念。但Rust在这块做了不少「反常识」的设计。我第一次接触时也觉得很别扭——为什么默认不可变?为什么还要搞个shadowing?
别急,我们一个个拆开看。
3.1 let与mut:默认不可变的设计哲学
在Rust里,用let声明的变量默认是不可变的。什么意思?就是值一旦绑定,就不能改了。
let x = 5;
x = 6; // 编译错误!
我第一次写Rust时就被这个坑过。心想「什么鬼,变量不能变还叫变量?」
但后来我理解了——Rust的设计者认为,默认不可变能让代码更安全、更容易推理。你想想看,如果一个值永远不会变,那你在阅读代码时就不用担心它被意外修改。
那如果真的需要改变呢?用mut关键字:
let mut y = 5;
y = 6; // 没问题
println!("y = {}", y);
3.2 变量遮蔽(Shadowing):同名变量的「变身术」
Rust允许你声明一个与之前变量同名的新变量。这叫「变量遮蔽」。
let x = 5;
let x = x + 1; // 遮蔽了之前的x
let x = x * 2;
println!("x = {}", x); // 输出12
这里有个关键点:遮蔽不是修改,而是创建了一个新变量。所以你可以改变变量的类型:
let spaces = " ";
let spaces = spaces.len(); // 从字符串变成了数字
如果用mut,上面这段代码会报错——你不能把字符串改成数字。但遮蔽可以,因为它是全新的变量。
3.3 常量(const)与静态变量(static)
这两个概念容易混淆。我简单说说区别。
const:编译期常量
const在编译时就被计算并内联到使用的地方。它必须标注类型,且值必须是编译期可确定的表达式。
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.1415926;
我个人习惯用const来定义「魔法数字」——比如配置参数、数学常量等。这样代码可读性会好很多。
static:静态变量
static类似全局变量,有固定的内存地址。它也可以声明为可变(static mut),但访问可变静态变量是不安全的,需要用unsafe块。
static APP_NAME: &str = "Rust入门";
static mut COUNTER: u32 = 0;
fn increment() {
unsafe {
COUNTER += 1;
}
}
const没有固定内存地址,每次使用都是复制static有固定内存地址,全局唯一const只能用于编译期常量表达式static可以用于运行时初始化的值(但需要lazy_static或once_cell)
3.4 变量作用域:花括号里的「小世界」
Rust的作用域规则很简单:变量从声明处开始生效,到所在花括号结束处失效。
fn main() {
let x = 5; // x从这里开始生效
{
let y = 10; // y只在这个块内有效
println!("内部: x={}, y={}", x, y);
} // y在这里失效
// println!("y = {}", y); // 编译错误!y已不存在
println!("外部: x={}", x);
} // x在这里失效
作用域和遮蔽经常一起出现。比如:
let x = 1;
{
let x = 2; // 遮蔽了外部的x
println!("内部x = {}", x); // 2
}
println!("外部x = {}", x); // 1,外部的x不受影响
嗯,这里要注意:遮蔽只在当前作用域内有效。离开作用域后,原来的变量会「复活」。
知识体系总览
下面这张图帮你理清本章的核心逻辑:
避坑指南:我踩过的那些坑
最后分享几个实战中容易犯的错误:
- 忘记加mut:刚开始写Rust时,我经常忘记加mut,然后编译器报错。后来养成了习惯——先写
let mut,等确定不需要修改时再删掉mut。 - 遮蔽导致混淆:有一次我在循环里遮蔽了外部变量,结果调试了半天才发现。建议遮蔽只用在类型转换这种明确场景。
- static mut的陷阱:可变静态变量在多线程环境下是数据竞争的温床。除非你很清楚自己在做什么,否则尽量用
const或Mutex包装的static。
好了,变量这块就讲到这里。记住一句话:Rust的变量设计,一切为了安全。理解了这个设计哲学,后面的所有权、生命周期学起来会顺畅很多。
公众号:蓝海资料掘金营,微信deep3321