3. 变量与可变性:从let到static的完整认知

变量是编程语言最基础的概念。但Rust在这块做了不少「反常识」的设计。我第一次接触时也觉得很别扭——为什么默认不可变?为什么还要搞个shadowing?

别急,我们一个个拆开看。

3.1 let与mut:默认不可变的设计哲学

在Rust里,用let声明的变量默认是不可变的。什么意思?就是值一旦绑定,就不能改了。

let x = 5;
x = 6; // 编译错误!

我第一次写Rust时就被这个坑过。心想「什么鬼,变量不能变还叫变量?」

但后来我理解了——Rust的设计者认为,默认不可变能让代码更安全、更容易推理。你想想看,如果一个值永远不会变,那你在阅读代码时就不用担心它被意外修改。

那如果真的需要改变呢?用mut关键字:

let mut y = 5;
y = 6; // 没问题
println!("y = {}", y);
我的习惯:先声明为不可变,等确实需要修改时再加mut。这样能减少很多不必要的bug。

3.2 变量遮蔽(Shadowing):同名变量的「变身术」

Rust允许你声明一个与之前变量同名的新变量。这叫「变量遮蔽」。

let x = 5;
let x = x + 1; // 遮蔽了之前的x
let x = x * 2;
println!("x = {}", x); // 输出12

这里有个关键点:遮蔽不是修改,而是创建了一个新变量。所以你可以改变变量的类型:

let spaces = "   ";
let spaces = spaces.len(); // 从字符串变成了数字

如果用mut,上面这段代码会报错——你不能把字符串改成数字。但遮蔽可以,因为它是全新的变量。

我曾经踩过的坑:过度使用遮蔽会让代码难以阅读。特别是当遮蔽发生在不同作用域时,很容易搞混当前用的是哪个变量。我的建议是:遮蔽用于类型转换或临时计算,不要用来「复用」变量名。

3.3 常量(const)与静态变量(static)

这两个概念容易混淆。我简单说说区别。

const:编译期常量

const在编译时就被计算并内联到使用的地方。它必须标注类型,且值必须是编译期可确定的表达式。

const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.1415926;

我个人习惯用const来定义「魔法数字」——比如配置参数、数学常量等。这样代码可读性会好很多。

static:静态变量

static类似全局变量,有固定的内存地址。它也可以声明为可变(static mut),但访问可变静态变量是不安全的,需要用unsafe块。

static APP_NAME: &str = "Rust入门";
static mut COUNTER: u32 = 0;

fn increment() {
    unsafe {
        COUNTER += 1;
    }
}
核心区别:
  • const没有固定内存地址,每次使用都是复制
  • static有固定内存地址,全局唯一
  • const只能用于编译期常量表达式
  • static可以用于运行时初始化的值(但需要lazy_staticonce_cell

3.4 变量作用域:花括号里的「小世界」

Rust的作用域规则很简单:变量从声明处开始生效,到所在花括号结束处失效

fn main() {
    let x = 5; // x从这里开始生效
    
    {
        let y = 10; // y只在这个块内有效
        println!("内部: x={}, y={}", x, y);
    } // y在这里失效
    
    // println!("y = {}", y); // 编译错误!y已不存在
    println!("外部: x={}", x);
} // x在这里失效

作用域和遮蔽经常一起出现。比如:

let x = 1;
{
    let x = 2; // 遮蔽了外部的x
    println!("内部x = {}", x); // 2
}
println!("外部x = {}", x); // 1,外部的x不受影响

嗯,这里要注意:遮蔽只在当前作用域内有效。离开作用域后,原来的变量会「复活」。

知识体系总览

下面这张图帮你理清本章的核心逻辑:

变量与可变性知识体系 变量声明 let 与 mut 变量遮蔽 (Shadowing) const 与 static 变量作用域 默认不可变 mut 开启可变性 同名变量重新声明 可改变类型 const: 编译期常量 static: 全局静态变量 花括号内生效 离开作用域失效 核心原则 默认不可变 → 需要时加mut → 遮蔽用于类型转换 const用于常量 → static用于全局状态 → 作用域控制生命周期

避坑指南:我踩过的那些坑

最后分享几个实战中容易犯的错误:

  • 忘记加mut:刚开始写Rust时,我经常忘记加mut,然后编译器报错。后来养成了习惯——先写let mut,等确定不需要修改时再删掉mut。
  • 遮蔽导致混淆:有一次我在循环里遮蔽了外部变量,结果调试了半天才发现。建议遮蔽只用在类型转换这种明确场景。
  • static mut的陷阱:可变静态变量在多线程环境下是数据竞争的温床。除非你很清楚自己在做什么,否则尽量用constMutex包装的static。

好了,变量这块就讲到这里。记住一句话:Rust的变量设计,一切为了安全。理解了这个设计哲学,后面的所有权、生命周期学起来会顺畅很多。


公众号:蓝海资料掘金营,微信deep3321