第4章 - 变量与可变性
嗨,朋友!我是长安。
上一章我们写了第一个 Rust 程序,但只是打印了一些固定的文字。要写真正有用的程序,我们需要学习变量。
在 Rust 中,变量有一个非常独特的特性:默认不可变。听起来有点奇怪?别担心,我会用最简单的方式给你解释清楚。
🤔 什么是变量?
变量就像是一个盒子,里面可以存放数据。比如:
- 存放一个数字:年龄、分数、价格
- 存放一段文字:姓名、地址、消息
- 存放布尔值:真或假
让我们看第一个例子:
fn main() {
let x = 5;
println!("x 的值是: {}", x);
}
运行后输出:
x 的值是: 5
这里:
let关键字用来声明变量x是变量名= 5表示把 5 这个值赋给 xprintln!打印 x 的值
长安说
在其他语言里,变量通常用 var、let、const 等关键字声明。Rust 统一使用 let,简单明了。
🔒 变量默认不可变
现在来看 Rust 最特别的地方:变量默认是不可变的(immutable)。
什么意思呢?看这个例子:
fn main() {
let x = 5;
println!("x 的值是: {}", x);
x = 6; // ❌ 错误!不能修改不可变变量
println!("x 的值是: {}", x);
}
如果你运行这段代码,编译器会报错:
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:5:5
|
3 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
5 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
编译器说:"x 是不可变的,你不能给它赋值两次!"
长安说
你可能会问:"为什么要这样设计?这不是很麻烦吗?"
其实这是 Rust 的一个核心设计理念:默认安全。不可变变量可以帮助你:
- 避免意外修改数据导致的 bug
- 让代码更容易理解(值不会突然变化)
- 提高程序的并发安全性
如果你真的需要修改变量,Rust 提供了明确的方式,我马上就会讲到。
🔓 可变变量(Mutable)
如果你需要修改变量的值,只需要在声明时加上 mut 关键字:
fn main() {
let mut x = 5; // 注意这里加了 mut
println!("x 的值是: {}", x);
x = 6; // ✅ 现在可以修改了!
println!("x 的值是: {}", x);
}
运行后输出:
x 的值是: 5
x 的值是: 6
mut 是 mutable(可变的)的缩写。
长安提醒
只有在真正需要修改变量时才加 mut。如果一个变量声明后从未被修改,编译器会给出警告,提醒你去掉不必要的 mut。
🎭 变量遮蔽(Shadowing)
Rust 有一个很有意思的特性叫变量遮蔽(shadowing)。你可以用相同的名字声明新变量,新变量会"遮蔽"旧变量:
fn main() {
let x = 5;
println!("第一个 x: {}", x);
let x = x + 1; // 用新值遮蔽旧的 x
println!("第二个 x: {}", x);
let x = x * 2; // 再次遮蔽
println!("第三个 x: {}", x);
}
输出:
第一个 x: 5
第二个 x: 6
第三个 x: 12
长安说
每次使用 let 声明变量时,实际上是创建了一个全新的变量,只是名字相同而已。旧变量被"遮蔽"了,不再能访问。
遮蔽 vs 可变变量
你可能会问:"遮蔽和可变变量有什么区别?看起来效果一样啊。"
其实有两个重要区别:
区别1:遮蔽可以改变类型
fn main() {
let spaces = " "; // 字符串类型
let spaces = spaces.len(); // 数字类型,这是允许的!
println!("空格数: {}", spaces);
}
但如果用 mut,就不能改变类型:
fn main() {
let mut spaces = " ";
spaces = spaces.len(); // ❌ 错误!类型不匹配
}
区别2:遮蔽创建的是不可变变量
fn main() {
let x = 5;
let x = x + 1; // x 依然是不可变的
x = 10; // ❌ 错误!不能修改
}
长安说
遮蔽在处理数据转换时特别有用。比如读取用户输入(字符串)然后转换成数字,可以用同一个变量名,代码更简洁。
📝 变量命名规则
Rust 的变量命名有以下规则:
- 只能包含:字母、数字、下划线
- 不能以数字开头
- 区分大小写(
age和Age是不同的变量) - 不能使用关键字(如
let、fn、mut等) - 推荐使用蛇形命名法(snake_case)
fn main() {
// ✅ 好的变量名
let age = 25;
let user_name = "长安";
let is_active = true;
let total_count = 100;
// ❌ 不好的变量名(虽然能编译通过)
let a = 25; // 太简短,不知道是什么
let userName = "长安"; // 应该用 snake_case 而不是 camelCase
let x1y2z3 = 100; // 没有意义的名字
}
长安说
好的变量名应该见名知意。别人(或者一个月后的你)看到变量名就知道它是干什么的。
🔢 未使用的变量
如果你声明了一个变量但从未使用,Rust 会给出警告:
fn main() {
let x = 5; // 警告:变量 `x` 从未使用
}
如果你确实需要声明但暂时不用的变量,可以在名字前加下划线 _:
fn main() {
let _x = 5; // 没有警告了
}
📖 常量(Constants)
除了变量,Rust 还有常量。常量和不可变变量很像,但有几个重要区别:
const MAX_POINTS: u32 = 100_000;
fn main() {
println!("最大分数: {}", MAX_POINTS);
}
常量的特点:
- 必须用
const声明,不能用let - 必须标注类型(这里是
u32) - 可以在任何作用域声明,包括全局作用域
- 只能设置为常量表达式,不能是运行时计算的值
- 命名用全大写 + 下划线(SCREAMING_SNAKE_CASE)
| 特性 | 不可变变量 | 常量 |
|---|---|---|
| 关键字 | let | const |
| 必须标注类型 | 否 | 是 |
| 能否遮蔽 | 是 | 否 |
| 作用域 | 块级 | 可全局 |
| 命名规范 | snake_case | SCREAMING_SNAKE_CASE |
长安说
什么时候用常量?当这个值:
- 在整个程序运行期间都不会变
- 需要在多个地方使用
- 具有特定的含义(比如最大值、配置项等)
例子:MAX_USERS、PI、API_URL 等。
💡 小结
在这一章,我们学会了:
- 使用
let声明不可变变量(Rust 的默认行为) - 使用
let mut声明可变变量 - 使用变量遮蔽(shadowing)创建同名的新变量
- 变量命名规则和最佳实践
- 使用
const声明常量
核心要点:
- 变量默认不可变,需要修改时显式加
mut - 遮蔽允许重用变量名,甚至可以改变类型
- 不可变性让代码更安全、更易理解
🚀 下一步
现在你知道如何声明变量了,但变量可以存放什么类型的数据呢?
下一章,我们会学习 Rust 的数据类型:整数、浮点数、布尔值、字符和字符串。
💪 练习题
动手试试这些练习:
- 声明一个不可变变量
age,赋值为你的年龄,然后打印它 - 声明一个可变变量
count,初始值为 0,然后增加到 5,打印每次的值 - 使用变量遮蔽,将一个字符串
"123"转换成数字(提示:.parse()方法) - 声明一个常量
PI,值为 3.14159,类型为f64
答案示例
fn main() {
// 练习1
let age = 25;
println!("我的年龄是: {}", age);
// 练习2
let mut count = 0;
println!("count: {}", count);
count = count + 1;
println!("count: {}", count);
count = count + 1;
println!("count: {}", count);
// ... 继续到 5
// 练习3
let number_str = "123";
let number_str: i32 = number_str.parse().unwrap();
println!("转换后的数字: {}", number_str);
// 练习4
const PI: f64 = 3.14159;
println!("π 的值约为: {}", PI);
}
