Skip to content

变量和可变性

Rust 中存在三种"存储值"的方式:变量常量静态变量。它们的核心区别在于:值存在哪里、能不能改、活多久。

变量

变量用 let 声明,默认不可变。这是 Rust 与大多数语言最显著的区别之一。

rust
let a = 123;       // 不可变变量,声明后不能再赋值
let mut b = 10;    // 可变变量,可以重新赋值
b = 20;            // ✅ 允许
// a = 456;        // ❌ 编译错误:cannot assign twice to immutable variable

默认不可变

不可变性是 Rust 安全保证的基础之一。默认不可变能防止意外修改变量,让代码更易于推理。当你需要修改时,显式加 mut 是一种明确的意图声明——告诉读者"这个变量是会变化的"。

几点规则:

  1. mut 修饰的是变量绑定,而不是类型本身
  2. let 只能在函数体或代码块内部使用,用于创建局部变量
  3. 类型可以由编译器自动推断,也可以手动指定: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;

规则:

  1. 必须显式标注类型
  2. 值必须是编译期常量表达式(不能是运行时才能知道的值)
  3. 不能使用 mut,永远不可变
  4. 命名规范:全大写 + 下划线分隔(SCREAMING_SNAKE_CASE
  5. 可以在任何作用域声明,包括全局作用域

静态变量(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 实现全局可变状态

基于 MIT 协议发布