Skip to content

变量和可变性

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

绑定

将值和变量关联的过程称为绑定(Binding),变量的绑定可以使用 let 关键字,如下:

rust
let a = 1;         // 将 1 绑定到变量 a
let mut b = 2;     // 将 2 绑定到变量 b

声明变量(Declaration)往往是在栈上分配一块空间。而在 Rust 中,let a = 1; 意味着你把名字 a 与内存地址 "绑定" 在了一起,更能体现所有权的概念。在内存和指针一节将详细介绍。

变量

变量分为不可变变量可变变量。不可变变量不能进行二次绑定,可变变量可以进行二次绑定。

不可变变量

  • 语法: 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)

常量用于定义在编译期就确定的固定值,是最纯粹的"名字绑定"。

rust
const NUM: i32 = 100_000;
const PI: f64 = 3.14159;

使用规则:

  1. 必须显式标注类型
rust
const A = 1;        // 错误: 没有显式标注类型
const A: i32 = 1;   // 正确,常量必须有类型注解
  1. 不能使用 mut,永远不可变
rust
const mut B: i32 = 2; // 错误: 不能使用 mut
const B: i32 = 2;     // 正确,常量永远不可变
  1. 值必须是编译期常量表达式,不能是运行时计算出的值
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)

静态变量拥有固定的内存地址,在程序的整个生命周期内都存在。

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

基于 MIT 协议发布