变量和可变性
Rust 中存在三种"存储值"的方式:变量、常量和静态变量。它们的核心区别在于: 值存在哪里、能不能改、活多久。
绑定
将值和变量关联的过程称为绑定(Binding),变量的绑定可以使用 let 关键字,如下:
rust
let a = 1; // 将 1 绑定到变量 a
let mut b = 2; // 将 2 绑定到变量 b声明变量(Declaration)往往是在栈上分配一块空间。而在 Rust 中,
let a = 1;意味着你把名字a与内存地址 "绑定" 在了一起,更能体现所有权的概念。在内存和指针一节将详细介绍。
变量
变量分为不可变变量和可变变量。不可变变量不能进行二次绑定,可变变量可以进行二次绑定。
- 命名规范: 遵循蛇形命名法(
snake_case) - 作用域: 只能在函数体或代码块内部使用,用于创建局部变量
不可变变量
- 语法:
let 变量名: 类型 = 值;
rust
let a = 1; // 类型由编译器推断为 i32
let b: i32 = 2; // 不可变变量,绑定后不能修改
b = 2; // ❌ 不可修改不可变变量的值类型可以由编译器自动推断,也可以手动指定:
let b: i32 = 2;
可变变量
- 语法:
let mut 变量名: 类型 = 值;
rust
let mut a: i32 = 1; // 可变变量
a = 2; // ✅ 允许修改
mut关键字表示变量是可变的,可以进行二次绑定。- 可变变量的本质是覆盖(Overwriting),它修改了原变量所在内存地址中的值,而不是创建一个新变量。与之相对的概念是遮蔽(Shadowing),后者会创建一个新变量,旧变量仍存在但不可访问。
常量(const)
常量用于定义在编译期就确定的固定值,是最纯粹的"名字绑定"。
- 语法:
const 常量名: 类型 = 值; - 命名规范: 大写蛇形命名法(
SCREAMING_SNAKE_CASE) - 作用域: 可以在任何作用域声明,包括全局作用域
rust
const NUM: i32 = 100_000;
const PI: f64 = 3.14159;使用规则:
- 必须显式标注类型
rust
const A = 1; // 错误: 没有显式标注类型
const A: i32 = 1; // 正确,常量必须有类型注解- 不能使用
mut,永远不可变
rust
const mut B: i32 = 2; // 错误: 不能使用 mut
const B: i32 = 2; // 正确,常量永远不可变- 值必须是编译期常量表达式,不能是运行时计算出的值
rust
const A: i32 = 10;
const B: i32 = 20;
const C: i32 = A + B; // ✅ 编译器在编译阶段就把 C 变成了 30
const D: i32 = 10 + 20; // ✅ 直接使用常量表达式
let x = 10;
const Y: i32 = x; // ❌ 常量必须由常量表达式赋值静态变量(static)
静态变量拥有固定的内存地址,在程序的整个生命周期内都存在。
- 语法:
static 变量名: 类型 = 值; - 命名规范: 大写蛇形命名法(
SCREAMING_SNAKE_CASE) - 作用域: 可以在任何作用域声明,包括全局作用域
rust
static GREETING: &str = "Hello";
static mut COUNTER: i32 = 0; // 可变静态变量,读写需要 unsafe修改静态变量必须在 unsafe 块中:
rust
unsafe {
COUNTER += 1;
println!("{COUNTER}");
}在实际项目中,全局可变状态应优先使用
Mutex<T>或原子类型(AtomicI32等),而不是static mut,后者在多线程下存在数据竞争风险。
遮蔽(Shadowing)
Rust 允许用同名的新变量"遮蔽"旧变量:
rust
let a = 1; // a 的值为 1
let a = a + 1; // ✅ 新的 a,值为 2
let a = "hello"; // ✅ 遮蔽还可以改变类型遮蔽和 mut 的区别:
mut 赋值 | let 遮蔽 | |
|---|---|---|
| 内存行为 | 修改原变量的值(同一块内存) | 创建新变量,旧值仍存在但不可访问 |
| 能否改类型 | ❌ 不能 | ✅ 可以 |
| 本质 | 覆盖: 同一个坑,换个东西填进去 | 遮蔽: 在旁边挖个新坑,把原来的坑挡住 |
mut本质是覆盖(Overwriting),它修改原变量内存地址中的值,而不是创建一个新变量。- 遮蔽(Shadowing)是创建一个新变量(恰好同名),旧变量仍存在但不可再通过新变量访问。
常量也可以被遮蔽(在内层作用域):
rust
const A: i32 = 10;
fn main() {
const A: i32 = 20; // ✅ 内层作用域遮蔽外层常量
println!("{A}"); // 20
}const 和 static 的区别
两者看起来相似,但内存行为完全不同:
| 特性 | const(常量) | static(静态变量) |
|---|---|---|
| 内存位置 | 没有固定地址,编译时内联到每个使用处 | 有固定地址,程序中只存在一份 |
| 可变性 | 永远不可变 | 可以 static mut,但需要 unsafe |
| 生命周期 | 无生命周期概念(类似宏展开) | 'static,贯穿整个程序 |
trait 关联 | 可以作为 trait 的关联常量 | 不能 |
| 泛型支持 | 支持泛型常量 | 不支持 |
直观理解:
const像"复制粘贴": 编译器把值直接硬编码到每个引用它的地方。如果是一个大数组且多处使用,会增大编译产物体积。static像"全局坐标": 所有引用都指向内存中同一个地址,数据只有一份。
如何选择:
- 优先用
const: 适合配置参数、数学常数、数组长度等场景,编译器可做更多优化 - 用
static的场景: 需要固定内存地址(如 FFI 调用)、数据量很大不想多次内联、需要配合Mutex实现全局可变状态