找回密码
 立即注册
首页 业界区 安全 Rust变量为啥要设计成默认不可变?

Rust变量为啥要设计成默认不可变?

余思洁 4 天前
Rust 定义变量默认是不可变的,如果可变,需要显式关键字 mut 声明。
  1. // 不允许修改
  2. let x = 12;
  3. // 允许修改
  4. let mut y = 13;
复制代码
如果你对不允许修改的变量进行修改,是会直接编译报错的。
1.png

那么 rust 为啥要这样设计呢?
1. 内存安全(Memory Safety)无需垃圾回收器(GC)

Rust 的核心目标是:在不依赖 GC 的前提下保证内存安全
而不可变变量(let x = ...)是达成这个目标的重要手段。
  1. // 编译错误 - 防止意外修改
  2. let x = 5;
  3. x = 10; // 编译器阻止
  4. // 必须显式声明可变性
  5. let mut y = 5;
  6. y = 10; // OK
复制代码

  • 如果一个变量不可变,那么:

    • 你不能随意修改它的值;
    • 编译器可以确信:只要这个变量存在,其值就是一致的;
    • 多线程访问中,不可变数据天然是线程安全的(无需加锁);
    • 编译器无需追踪谁在修改它 → 避免数据竞争。

底层原理:编译器在构建“借用检查器”(borrow checker)时,可以将不可变绑定视为只读引用的生命周期,提升静态分析能力。
2. 易于静态分析与编译优化

不可变性让 Rust 编译器在编译期间更容易进行静态分析与优化:

  • 不可变数据不会变化 → 编译器可以提前内联、缓存、优化;
  • 编译器能推导出更精准的生命周期与借用范围;
  • 代码行为更可预测,避免未定义行为(undefined behavior)。
举例:LLVM 后端可以对不可变变量做 aggressive constant folding(通过编译器优化技术对程序中重复出现的常量表达式进行折叠处理,以减少运行时计算量并优化代码体积)、值传播、死代码消除等。
  1. let x = expensive_calculation();
  2. // 编译器知道 x 不会改变,可以:
  3. // 1. 将 x 存储在寄存器中
  4. // 2. 消除重复的内存读取
  5. // 3. 进行常量传播优化
  6. use_value(x);
  7. use_value(x); // 4.编译器可以重用之前的值
复制代码
3. 避免数据竞争(Data Race)

Rust 中的数据竞争是编译错误,而不是运行时错误。默认不可变正好是最好的保护:

  • 数据竞争是指:两个线程同时访问同一变量,且至少一个是写;
  • 默认不可变意味着:你要写变量必须显式 mut 标注,或者加锁;
  • 所有“可变”的行为都必须显式 → 更容易在代码审查中发现问题。
比如在Java语言中,这段代码编译是不会报错的,但是输出结果无法保证,可能为 1(本应是 2)。
这种问题 运行时才暴露问题,可能非常隐蔽且难以调试
  1. public class RaceConditionExample {
  2.     private static int counter = 0;
  3.     public static void main(String[] args) throws InterruptedException {
  4.         Thread t1 = new Thread(() -> {
  5.             counter++;  // 不是原子操作
  6.         });
  7.         Thread t2 = new Thread(() -> {
  8.             counter++;  // 不是原子操作
  9.         });
  10.         t1.start();
  11.         t2.start();
  12.         t1.join();
  13.         t2.join();
  14.         System.out.println("Counter: " + counter);
  15.     }
  16. }
复制代码
但是在rust 中会直接编译失败。
  1. use std::thread;
  2. fn main() {
  3.     let mut counter = 0;
  4.     let handle1 = thread::spawn(|| {
  5.         counter += 1; // ❌ 编译失败
  6.     });
  7.     let handle2 = thread::spawn(|| {
  8.         counter += 1; // ❌ 编译失败
  9.     });
  10.     handle1.join().unwrap();
  11.     handle2.join().unwrap();
  12. }
复制代码
运行报错:
2.png

Rust 编译器发现你把一个非线程安全的可变变量 counter 移入了两个线程闭包中,而没有同步机制,于是直接编译错误。
4. 表达设计意图,提升可读性与可维护性

不可变变量也是一种语义提示

  • let x = 5 → 表示 x 不会改变;
  • let mut x = 5 → 表示 x 的值会变化。
这种强制标记有助于代码维护者理解变量生命周期与职责。
5. 默认安全(safe by default)

Rust 的设计哲学之一是:安全默认(safe by default),也就是说:

  • 只有当程序员显式请求“危险行为”(如 unsafe、mut、裸指针等)时才打开风险;
  • 变量默认不可变 → 编译器可以保证只读访问不会引起内存错误;
  • 需要写权限时,你得自己“承担责任”去标明。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册