变量和可变性
Rust 中存在三种"存储值"的方式:变量、常量和静态变量。它们的核心区别在于:值存在哪里、能不能改、活多久。
变量
变量用 let 声明,默认不可变。这是 Rust 与大多数语言最显著的区别之一。
rust
let a = 123; // 不可变变量,声明后不能再赋值
let mut b = 10; // 可变变量,可以重新赋值
b = 20; // ✅ 允许
// a = 456; // ❌ 编译错误:cannot assign twice to immutable variable默认不可变
不可变性是 Rust 安全保证的基础之一。默认不可变能防止意外修改变量,让代码更易于推理。当你需要修改时,显式加 mut 是一种明确的意图声明——告诉读者"这个变量是会变化的"。
几点规则:
mut修饰的是变量绑定,而不是类型本身let只能在函数体或代码块内部使用,用于创建局部变量- 类型可以由编译器自动推断,也可以手动指定:
let a: i32 = 123;
遮蔽(Shadowing)
Rust 允许用同名的新变量"遮蔽"旧变量:
rust
let a = 123;
let a = a + 1; // 新的 a,值为 124
let a = "hello"; // ✅ 遮蔽还可以改变类型遮蔽和 mut 的本质区别:
mut 赋值 | let 遮蔽 | |
|---|---|---|
| 内存行为 | 修改原变量的值(同一块内存) | 创建新变量,旧值仍存在但不可访问 |
| 能否改类型 | ❌ 不能 | ✅ 可以 |
| 本质 | 覆盖:同一个坑,换个东西填进去 | 遮蔽:在旁边挖个新坑,把原来的坑挡住 |
遮蔽(Shadowing)不是覆盖,覆盖会修改原地址中的值,发生遮蔽时只是指针指向新的值,原值依旧存在,只是无法通过变量找到原值。
常量也可以被遮蔽(在内层作用域):
rust
const A: i32 = 10;
fn main() {
const A: i32 = 20; // ✅ 内层作用域遮蔽外层常量
println!("{A}"); // 20
}常量(const)
常量用于定义在编译期就确定的固定值,是最纯粹的"名字绑定"。
rust
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;规则:
- 必须显式标注类型
- 值必须是编译期常量表达式(不能是运行时才能知道的值)
- 不能使用
mut,永远不可变 - 命名规范:全大写 + 下划线分隔(
SCREAMING_SNAKE_CASE) - 可以在任何作用域声明,包括全局作用域
静态变量(static)
静态变量拥有固定的内存地址,在程序的整个生命周期内都存在。
rust
static GREETING: &str = "Hello";
static mut COUNTER: i32 = 0; // 可变静态变量,读写需要 unsafe修改静态变量必须在 unsafe 块中:
rust
unsafe {
COUNTER += 1;
println!("{COUNTER}");
}在实际项目中,全局可变状态应优先使用
Mutex<T>或原子类型(AtomicI32等),而不是static mut,后者在多线程下存在数据竞争风险。
const 和 static 的区别
两者看起来相似,但内存行为完全不同:
| 特性 | const(常量) | static(静态变量) |
|---|---|---|
| 内存位置 | 没有固定地址,编译时内联到每个使用处 | 有固定地址,程序中只存在一份 |
| 可变性 | 永远不可变 | 可以 static mut,但需要 unsafe |
| 生命周期 | 无生命周期概念(类似宏展开) | 'static,贯穿整个程序 |
trait 关联 | 可以作为 trait 的关联常量 | 不能 |
| 泛型支持 | 支持泛型常量 | 不支持 |
直观理解:
const像"复制粘贴":编译器把值直接硬编码到每个引用它的地方。如果是一个大数组且多处使用,会增大编译产物体积。static像"全局坐标":所有引用都指向内存中同一个地址,数据只有一份。
如何选择:
- 优先用
const:适合配置参数、数学常数、数组长度等场景,编译器可做更多优化 - 用
static的场景:需要固定内存地址(如 FFI 调用)、数据量很大不想多次内联、需要配合Mutex实现全局可变状态