首页 / Rust 入门教程 / 变量与可变性

Rust 入门教程

变量与可变性

本教程共 78 篇 · 第 7 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

RustRust 入门教程变量letmut可变性不可变

本节目标:搞懂 Rust 里变量默认不可变这个反直觉设定,学会用 let mut 声明可变变量,并理解它背后的安全考量。

在很多语言里,变量天生就能随便改。Rust 偏偏反着来:默认情况下,你声明的变量改不了。这一章就讲这个最容易被忽略、却贯穿全程的特性。

7-1 声明变量用 let

Rust 用 let 关键字声明变量:

fn main() {
    let x = 5;
    println!("x 的值是 {x}");
}

let x = 5; 意思是”把 5 绑到名字 x 上”。之后你可以用 {x} 这种写法把变量塞进字符串里打印。注意这里没有写类型——Rust 能根据 5 推断出 x 是整数,这叫类型推断,省得你每句都标类型。

7-2 默认不可变:改它会报错

现在试试给 x 重新赋值:

fn main() {
    let x = 5;
    x = 6; // 错!
    println!("x 的值是 {x}");
}

编译器会红字报错,大意是”不能给不可变变量 x 二次赋值”。这就是 Rust 的第一个反直觉点:变量默认是只读的

Warning

从 Python、JavaScript 过来的人,十有八九在这一步栽跟头:明明写了 x = 6,编译器却说不行。记住,Rust 里 let 绑定的东西默认锁死,想改必须显式声明可变。

7-3 用 mut 让它可变

如果你确实需要改,在 let 后面加 mut(mutable 的缩写):

fn main() {
    let mut x = 5;
    println!("修改前:{x}");
    x = 6;
    println!("修改后:{x}");
}

let mut x = 5; 明确宣告”x 是我打算改的”。这下赋值就合法了,运行输出:

修改前:5
修改后:6

7-4 为什么要默认不可变

你可能会想:多此一举,我写 mut 不就完了?其实这设计是有深意的。

不可变意味着”这个值在这个作用域里不会变”。一旦确定,你就不用到处猜”它是不是被谁偷偷改了”。在多线程、大项目里,这种确定性极其珍贵——很多 bug 正是源于”某个值被意料之外地改了”。Rust 把”会变”变成一件需要你主动声明的事,等于逼你在写代码时刻意想清楚:这个值到底该不该变?

Note

这跟性能不冲突。不可变变量编译器还能做更多优化。而且它和”所有权”机制配合,是 Rust 内存安全的基石之一。

7-5 类型标注(可选但重要)

虽然能推断,但有时你希望明确类型,就在变量名后加冒号:

let x: i32 = 5;
let y: f64 = 3.14;

i32 是 32 位整数,f64 是 64 位浮点数。这种写法在类型不明显、或你想强制某种类型时很有用。新手阶段编译器推断够用,但知道怎么标类型不吃亏。

7-6 常量与变量的区别(预告)

你可能会问:既然变量默认不可变,那它和”常量”有啥区别?区别在于,变量即使不可变,也还是在运行时绑定的、可以靠后面讲的 shadowing 重绑;而常量是用 const 声明的、编译期就固定的、必须标类型、且不能加 mut。下一章专门讲常量,这里先有个区分即可。

7-7 一个常见误区:mut 不是万能的

mut 只解除”能不能改值”的限制,不解除 Rust 其他规则。比如下面这种仍然不行:

let mut s = String::from("hello");
s = 5; // 错!类型不能变

mut 允许你改值,但不允许你把”字符串”改成”数字”——类型一旦定下就锁死。这一点和很多动态语言不同,Rust 是静态类型,变量的类型终身不变。

Tip

养成习惯:声明变量时先别加 mut,等编译器报”你不能改”时,再回过头想”我真的需要改吗?需要就加 mut”。这种被迫的停顿,长期看是在帮你少写 bug。

7-8 不可变带来的调试优势

不可变变量不只是”语法规矩”,在调试时有实实在在的好处。当你发现某个值不对劲,如果它被声明成不可变,你就知道它只可能在声明那一行被赋初值,绝不会在某个遥远的函数里被偷偷改掉。排查范围一下子小了很多。相反,一个变量到处是 mut,你得把所有改动它的地方都翻一遍。

Tip

一个实用习惯:默认不加 mut。等编译器报”不能给不可变变量赋值”时,你才停下来想”这里真的需要改吗”。很多时候用 shadowing 或换个写法就能避免 mut,代码反而更清晰。

初学者常把 shadowing 和 mut 搞混。比如 let mut x = 5; x = x + 1; 和 shadowing 的 let x = x + 1; 结果都是 6,但本质不同:前者改同一个变量,后者用新变量盖住旧的。编译器报错会明确区分——看到 “cannot assign twice to immutable variable” 是忘了 mut,看到 “unused variable” 加 “shadowed” 提示是用 shadowing。学会读这两种报错,能少走很多弯路。

7-9 综合例子:温度转换

用一个综合小例子巩固变量与可变性:写个摄氏转华氏的函数,体会不可变变量如何串起计算步骤。

fn celsius_to_fahrenheit(c: f64) -> f64 {
    let step1 = c * 9.0;
    let step2 = step1 / 5.0;
    let result = step2 + 32.0;
    result
}

这里 step1step2 都是不可变绑定,每步取名让中间结果清晰可读,编译器也能确认它们不会被意外改掉。如果你想”边算边改”,用 let mut 也行,但这个例子里拆成多个不可变绑定往往更易读、更易测。

Tip

写计算类函数时,把长公式拆成几个有名字的中间变量(都不可变),比塞进一行又长又难读的表达式好。变量名即文档,三个月后回看也能秒懂每一步在算什么。这正是”默认不可变”带来的隐性好处:每个名字都代表一个确定值。

7-10 变量与作用域

变量除了”可变与否”,还受”作用域”约束:一个变量只在它所在的 {} 块内有效,出了块就消失。这和所有权”离开作用域就释放”是同一回事——作用域是所有权释放的触发器。

{
    let temp = 42;
    println!("{temp}"); // OK,temp 在作用域内
}
// println!("{temp}"); // 错,temp 已离开作用域

理解作用域,能帮你预判”哪个变量在哪儿还能用、什么时候被释放”。嵌套块里内层能读到外层的变量,但外层读不到内层的,这叫”内层可见外层、外层不可见内层”。

Note

作用域和可变是两件事:可变(mut)管”能不能改值”,作用域管”在哪段代码里看得见”。新手容易把两者搅在一起,记住它们是独立的两个维度即可。

7-11 小结

let 声明、默认不可变;要改就 let mut。这个设定看着别扭,实则是 Rust 安全哲学的入口。下一章我们看常量和一种特殊的”重绑定”技巧 shadowing,它们和变量息息相关却各有规矩。