3. 变量与可变性:let与mut,常量const,变量遮蔽(shadowing),作用域与所有权初探
好,咱们进入第三章。说实话,这一章是 Rust 里最「反直觉」的部分之一。我刚从 C++ 转过来时,被编译器怼得满头包。但别怕,一旦你理解了 Rust 的设计哲学,你会爱上这种严谨。
3.1 默认不可变:let 与 mut
在 Rust 里,变量默认是不可变的。什么意思?就是你声明了一个变量后,不能改它的值。
fn main() {
let x = 5;
x = 6; // 编译错误!
}
我第一次写这段代码时,心想:「什么鬼?变量不就是用来变的吗?」但 Rust 的设计者不这么想。他们觉得,默认不可变能让代码更安全、更容易推理。
那怎么让变量可变呢?加个 mut 关键字:
fn main() {
let mut x = 5;
x = 6; // 没问题
println!("x = {}", x);
}
let,等编译器报错了再加 mut。这样能逼自己思考:「这个变量真的需要变吗?」很多场景下,其实不需要。
3.2 常量:const
常量跟不可变变量有点像,但有本质区别。常量用 const 声明,且必须标注类型:
const MAX_POINTS: u32 = 100_000;
注意几个点:
- 常量在编译时就确定了值
- 常量可以在任何作用域声明,包括全局
- 常量不能加
mut,它永远不可变
我在项目中常用常量来定义配置值,比如超时时间、最大连接数。这样改配置时只需要改一处,不会到处找魔法数字。
let 重复声明(遮蔽),但常量不行。常量名在整个作用域内必须唯一。
3.3 变量遮蔽(Shadowing)
这个特性很有意思。你可以用同一个变量名声明新变量,新变量会「遮蔽」旧变量:
fn main() {
let x = 5;
let x = x + 1; // 遮蔽,x 现在是 6
let x = x * 2; // 再次遮蔽,x 现在是 12
println!("x = {}", x);
}
遮蔽跟 mut 不同。用 mut 是修改同一个变量的值,而遮蔽是创建了一个全新的变量。这意味着你可以改变变量的类型:
let spaces = " ";
let spaces = spaces.len(); // 从字符串变成了数字
如果用 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 在这里引入了所有权(Ownership)的概念——这是它最核心、也最独特的设计。
所有权三条规则:
- 每个值都有一个所有者(owner)
- 同一时间只有一个所有者
- 所有者离开作用域时,值被丢弃
看个例子:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 转移到了 s2
// println!("{}", s1); // 编译错误!s1 已经失效
println!("{}", s2); // 没问题
}
为什么?因为 String 类型的数据存在堆上,复制整个数据代价太大。Rust 选择「移动」所有权,而不是复制。这样既高效又安全。
如果你真的需要复制,可以显式调用 clone():
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2); // 两个都有效
但要注意,clone() 会深拷贝数据,有性能开销。我一般只在确实需要独立数据时才用。
对于整数、布尔、浮点数这类「标量类型」,它们实现了 Copy trait,赋值时会自动复制,不会转移所有权:
let x = 5;
let y = x; // 复制,不是移动
println!("x = {}, y = {}", x, y); // 两个都有效
嗯,这里要记住:Copy 类型都是存在栈上的,复制成本很低。而堆上的类型(如 String、Vec)默认是移动语义。
知识体系图
下面这张图帮你理清本章的核心逻辑:
这张图把四个概念串起来了。你会发现,所有权是 Rust 的基石,而变量遮蔽、不可变性这些设计,都是为了让所有权系统更易用、更安全。
好了,这一章的内容就这些。记住:多写、多编译、多看错误信息。Rust 的编译器是你最好的老师——它骂你骂得越狠,你学得越快。