文章目录
-
- 一、回顾与导入
- 二、变量:默认是不可变的
- [三、变量 vs 常量](#三、变量 vs 常量)
- 四、Shadowing(变量遮蔽)
-
- [Shadowing vs `mut`:有什么区别?](#Shadowing vs
mut:有什么区别?) - 实际应用场景
- [Shadowing vs `mut`:有什么区别?](#Shadowing vs
- 五、完整代码示例
- 六、总结与速查卡
- 七、思考题
- 参考链接
理解 Rust 的变量绑定机制 ------ 为什么默认不可变?什么是 shadowing?
一、回顾与导入
上一篇文章我们完成了 Rust 的环境搭建,并跑通了第一个 Hello World 程序。
在正式写代码之前,我们需要先理解 Rust 中最基础的概念:变量。Rust 的变量设计和你之前接触的语言(C/Java/Python/JS)有很大不同,让我们来看看。
二、变量:默认是不可变的
在 Rust 中,变量默认是不可变的(immutable)。这意味着一旦给变量绑定了值,就不能再修改它。
rust
fn main() {
let x = 5;
println!("x = {}", x);
x = 6; // ❌ 编译错误!
println!("x = {}", x);
}
编译时会报错:
log
error[E0384]: cannot assign twice to immutable variable `x`
💡 这其实是 Rust 的设计理念之一:默认安全,需要可变才显式指定。这样可以避免很多意外修改引起的 bug。
如何让变量可变?
使用 mut 关键字(mutable 的缩写):
rust
fn main() {
let mut x = 5;
println!("x = {}", x);
x = 6; // ✅ 可以修改了
println!("x = {}", x);
}
输出:
log
x = 5
x = 6
三、变量 vs 常量
Rust 中还有 const(常量),它和 let 变量有几个关键区别:
| 特性 | let 变量 |
const 常量 |
|---|---|---|
是否可变为 mut |
可 | ❌ 永远不可变 |
| 作用域 | 块作用域 | 全局或模块作用域 |
| 值必须编译时已知 | ❌(运行时也行) | ✅ 必须是常量表达式 |
| 类型标注 | 可省略(类型推断) | ✅ 必须显式标注 |
rust
const MAX_POINTS: u32 = 100_000; // 常量名约定全大写,下划线分隔
const PI: f64 = 3.14159265359;
fn main() {
println!("Max points: {}", MAX_POINTS);
println!("PI: {}", PI);
// MAX_POINTS = 200_000; // ❌ 不能修改常量
}
什么时候用 const?
- 数学常量(PI、E)
- 配置值(最大连接数、超时时间)
- 任何在程序运行期间不会改变的值
四、Shadowing(变量遮蔽)
这是 Rust 中一个非常有趣且实用的特性:可以用同名变量来"遮蔽"之前的变量。
rust
fn main() {
let x = 5;
let x = x + 1; // 新的 x 遮蔽了旧的 x
{
let x = x * 2; // 在内部作用域中再次遮蔽
println!("内部作用域: x = {}", x); // 输出: 12
}
println!("外部作用域: x = {}", x); // 输出: 6
}
Shadowing vs mut:有什么区别?
很多人会混淆这两个概念,我们来对比一下:
| 操作 | let x = ...(shadowing) |
let mut x = ... |
|---|---|---|
| 改变值类型 | ✅ 可以改变类型 | ❌ 不能改变类型 |
| 改变值 | ✅ 重新绑定 | ✅ 通过赋值 |
| 内存地址 | 改变(新绑定了内存) | 不变 |
重点来了:shadowing 可以改变变量的类型!
rust
fn main() {
let x = "hello"; // 字符串类型 &str
let x = x.len(); // 变成了 usize 类型 ✅
println!("x = {}", x); // 输出: 5
// 如果用 mut,则不能改类型
let mut y = "world";
// y = y.len(); // ❌ 编译错误:不能把 usize 赋值给 &str
}
这个特性在需要"转换"一个值时非常有用,比如字符串转数字,并保持变量名不变。
实际应用场景
rust
fn main() {
// 场景1:数据转换过程中复用变量名
let data = " 42 ";
let data = data.trim(); // &str -> &str
let data = data.parse::<i32>().unwrap(); // &str -> i32
println!("计算结果: {}", data + 10); // 52
// 场景2:处理用户输入
let input = " hello ";
let input = input.trim(); // 去除空格后重新绑定
println!("处理后的输入: '{}'", input); // 'hello'
}
五、完整代码示例
把今天学的内容整合起来:
rust
// 常量声明在模块作用域
const APP_NAME: &str = "Rust Learning";
const VERSION: u32 = 1;
fn main() {
println!("=== 常量示例 ===");
println!("App: {}, v{}", APP_NAME, VERSION);
println!("\n=== 可变变量示例 ===");
let mut counter = 0;
while counter < 3 {
println!("counter = {}", counter);
counter += 1;
}
println!("\n=== Shadowing 示例 ===");
let value = " 123 ";
println!("原始: '{}'", value);
let value = value.trim();
println!("trim后: '{}'", value);
let value = value.parse::<i32>().unwrap();
println!("转换数字后: {}", value);
let value = value * 2;
println!("乘以2后: {}", value);
}
运行结果:
log
=== 常量示例 ===
App: Rust Learning, v1
=== 可变变量示例 ===
counter = 0
counter = 1
counter = 2
=== Shadowing 示例 ===
原始: ' 123 '
trim后: '123'
转换数字后: 123
乘以2后: 246
六、总结与速查卡
| 概念 | 语法 | 特点 |
|---|---|---|
| 不可变变量 | let x = 5 |
默认,不能修改值 |
| 可变变量 | let mut x = 5 |
可以修改值,但不能改变类型 |
| 常量 | const MAX: u32 = 100 |
永远不可变,编译时确定,要显式类型 |
| Shadowing | let x = ...; let x = ... |
重新绑定,可改变类型,旧变量被遮蔽 |
最佳实践建议:
- 优先使用不可变变量(
let),必要时再加mut - 常量大写加下划线,放在文件顶部
- 需要转换类型时优先考虑 shadowing,而不是创建新变量名(比如
x_str、x_int这种)
七、思考题
- 下面的代码会输出什么?为什么?
rust
fn main() {
let x = 10;
let x = "hello";
println!("{}", x);
}
mut和 shadowing 哪个性能更好?为什么?