2. 变量绑定、可变性与作用域实践
从一段可编译的程序开始
下面的程序把 let、mut、shadowing 和块级作用域放在同一个 main 里,通过打印结果观察变量在生命周期中的变化。请先新建一个二进制项目,把代码写入 src/main.rs。
fn main() {
// 1. 不可变绑定:默认不可修改
let x = 5;
println!("x = {x}");
// 2. 可变绑定:mut 允许重新赋值,但类型不能变
let mut count = 0;
count += 1;
count += 1;
println!("count = {count}");
// 3. shadowing:用 let 重新绑定同名变量,可改变类型
let x = x + 1; // 新变量遮蔽旧 x,旧 x 生命周期结束
let x = x * 2;
println!("shadowed x = {x}");
// 4. 块级作用域:内层变量只在内层块中有效
let outer = 10;
{
let inner = outer + 5;
println!("inner = {inner}");
// inner 在此块结束后被丢弃
}
// println!("{inner}"); // 取消注释会编译失败
// 5. 作用域与所有权前置约束
let s = String::from("rust");
{
let s = s; // 把外层 s 移动进内层,外层 s 失效
println!("inner s = {s}");
}
// println!("{s}"); // 取消注释会编译失败:s 已被移动
}
运行 cargo run,预期输出为:
x = 5
count = 2
shadowed x = 12
inner = 15
inner s = rust
用编译器错误验证约束
Rust 的变量规则不是靠运行时检查,而是编译期强制。把下面三行分别取消注释,观察 cargo build 的报错信息,可以确认每条规则对应的错误类型。
| 取消注释的代码 | 编译器错误 | 说明 |
|---|---|---|
x = 6; | E0384 cannot assign twice to immutable variable | 不可变绑定不能二次赋值 |
println!("{inner}"); | E0425 cannot find value `inner` in this scope | 块级作用域外变量不可见 |
println!("{s}"); | E0382 borrow of moved value: `s` | String 被移动后原绑定失效 |
注意:
let x = x + 1; 中的右侧 x 仍然是旧变量,新绑定在语句结束后才生效。shadowing 不是修改,而是创建新变量并遮蔽旧名字。变量生命周期与作用域关系
下图把上述程序中的绑定、作用域边界和所有权移动画在同一张时间轴上,帮助区分“遮蔽”和“移动”这两种容易混淆的现象。
实践检查清单
- 确认
let x = 5;后直接x = 6;会触发 E0384,理解“默认不可变”是编译期约束。 - 确认
let mut count = 0;后count += 1;可以编译,且count类型始终为整数。 - 确认 shadowing 后新变量可以改变类型,例如
let x = 5; let x = "text";合法。 - 确认块内
let inner在块外不可见,触发 E0425。 - 确认
String被移动后原绑定不可用,触发 E0382,理解所有权前置约束。
如果想让外层
s 在块结束后仍然可用,可以改用引用 let r = &s;,这样只借用不移动;引用与生命周期的细节将在后续案例中展开。