Rust 学习笔记 02:变量与可变性
“Stability is not immobility.” – Prince Metternich
习惯了 Go 语言的 var 和 :=,第一次写 Rust 时,最让人抓狂的报错一定是 cannot assign twice to immutable variable。
在 Go 里,变量生来就是可变的(除了 const),但在 Rust 里,变量生来就是不可变的。
这就像是:Go 是一个自由奔放的游乐场,你想去哪就去哪;而 Rust 是一个戒备森严的博物馆,除非你申请了"触摸许可"(mut),否则只能看,不能摸。
1. 默认不可变 (Immutability)
在 Rust 中,当你写下:
let x = 5;
注意: Rust 必须在语句末尾加上 “;",与 Java 一样,但是 Go 却不需要,这需要 Go 开发者习惯很久。
这不仅是定义了一个变量,更是立下了一个誓言:“x 的值就是 5,永远不会变。”
如果你试图打破誓言:
x = 6; // 编译报错!
编译器会无情地告诉你:cannot assign twice to immutable variable x。
这种设计是为了安全。在并发编程中,如果一个数据大家都只读不写,那就根本不需要锁。Rust 从根源上消灭了很多并发 Bug。
那我想改怎么办?
很简单,加上 mut 关键字,申请"触摸许可”:
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is now: {}", x);
对于 Go 开发者来说,这需要适应一下。在 Go 里我们很少刻意区分可变/不可变,但在 Rust 里,这是你做每一个决定时的第一考量。
2. 常量 (Constants)
Rust 也有常量,用 const 定义。和 Go 的 const 类似,但有几点不同:
- 必须显式标注类型(Go 可以推导)。
- 可以在任何作用域定义,包括全局。
- 全大写 + 下划线命名(习惯约定)。
const MAX_POINTS: u32 = 100_000;
3. 变量遮蔽 (Shadowing)
这是 Rust 一个非常有趣(风骚)的特性,Go 里没有(或者说不推荐这么做,Go 里同名变量容易导致 bug)。
在 Rust 里,你可以重复定义同名变量:
let x = 5;
let x = x + 1; // 新的 x 遮蔽了旧的 x
let x = x * 2;
println!("The value of x is: {}", x); // 12
这看起来很乱?实际上非常有用。
比如在 Go 里,我们转换类型时常这样写:
str := "42"
num, _ := strconv.Atoi(str) // 不得不换个名字,或者用 num 覆盖
在 Rust 里:
let spaces = " ";
let spaces = spaces.len(); // 类型都变了!从 &str 变成了 usize
我们不需要发明 spaces_str 和 spaces_num 这种名字,直接 Shadowing 掉之前的变量,反正之前的也不用了。这在处理临时变量转换时非常爽。
4. 基础数据类型概览
Rust 是静态强类型语言。类型推导比 Go 强,大多数时候不用写类型,但也可以显式标注。
标量类型
- 整型:
i8,u8,i16,u16,i32(默认),u32,i64,u64,i128,u128,isize,usize(类似 Go 的int)。- 吐槽:Go 的
int到底是 32 还是 64 取决于系统,Rust 分得清清楚楚,而且原生支持 128 位基础类型整数,而 Go 需要使用big包,我猜这也是为什么 solana 使用 rust 作为开发语言的原因之一吧。
- 吐槽:Go 的
- 浮点型:
f32,f64(默认)。 - 布尔型:
bool(true, false)。 - 字符型:
char。注意!Rust 的 char 是 4 字节的 Unicode 标量值,可以存 Emoji 😻,不仅仅是 ASCII。这和 Go 的rune类似,但 Go 的string底层是 byte 数组,Rust 的 String 处理稍微复杂点(后面讲)。
复合类型
- 元组 (Tuple):
let tup: (i32, f64, u8) = (500, 6.4, 1);。Go 没有元组,只能通过多返回值模拟。 - 数组 (Array):
let a = [1, 2, 3, 4, 5];。长度固定,分配在栈上。这和 Go 的数组一样(不是 Slice)。
5. 小结
第二天,我们重新认识了"变量"。
- Rust 的
let默认是不可变的,想变要加mut。 - Shadowing 是个好东西,可以用来转换类型,复用变量名。
- 类型系统非常严谨,
i32就是i32,不能自动转成i64。
下一篇,我们将探讨函数。Rust 的函数有一个让 Go 开发者很不习惯的特性:表达式返回值(不用写 return)。
练习题:
- 定义一个不可变变量,尝试修改它,看看编译器的报错信息。
- 使用 Shadowing,将一个字符串类型的数字(如 “123”)转换为数字类型,并计算它的平方。
思考题:
为什么 Rust 允许 Shadowing 而 Go 不允许(或不推荐)?在什么场景下 Shadowing 可能会导致混淆?
本文代码示例: