Rust 入门:编译器是你最严格的朋友 书架

第 03 章 / 共 12 章

第三章 · 变量默认不许变

一条反常识的规矩

在 Rust 里声明一个变量:

let x = 5;

然后你顺手想改它:

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

编译器拒绝了你,理由只有一句:变量默认是不可变的(immutable)——大白话:「没说能改,就不许改」。想声明一个能改的变量,必须明确加上 mut(mutable,可变的):

let mut x = 5;   // 这次明确说了「可变」
x = 6;            // 没问题

这跟几乎所有主流语言反着来。别处的默认值是「随便改」,想锁死才要加关键字(Java 的 final、JS 的 const);Rust 的默认值是「锁死」,想改才要申请。为什么把默认值设成这样?因为 Rust 的设计者看透了第一章说的那个教训:程序出错的方式千奇百怪,但「一个值在你没注意的时候变了」是最高频的一种。把「不变」设为默认,等于让每个变量自证「我真的需要变」,大量的状态混乱在源头就消失了。等第十二章讲到并发——多个线程同时读写一块内存——你会看到这条小规矩怎么变成救命的堤坝。

类比:普通语言里每个文件默认「谁都能编辑」,出错了再查谁动的;Rust 里每个文件默认「只读」,要编辑得先申请权限。看起来麻烦,但文件夹里再不会出现「咦,这行谁改的?」

类型:编译器会猜,也允许你明说

Rust 是静态类型语言:每个变量的类型在编译期就必须确定下来。但写 let x = 5; 时你并没说类型——编译器会推断:5 是个整数,默认按 i32(32 位有符号整数)处理。大多数时候你就这么写,让编译器猜。猜不准或你想明说时,用冒号标注:

let price: f64 = 9.99;        // 64 位浮点数(带小数的数)
let count: u32 = 1_000_000;   // 32 位无符号整数(不装负数),下划线只是千分位
let done: bool = false;       // 布尔值
let ch: char = '蟹';           // 字符,注意是单引号;一个 char 能装下汉字

整数类型按「多少位、能不能装负数」分成一串:i8/i16/i32/i64/i128(带符号)和 u8/u16/u32/u64/u128(无符号)。拿不准就用默认的 i32,这是社区手感。浮点类似,默认 f64

有个细节值得知道:Rust 的整数运算不许悄悄溢出u8 最大只能装 255,如果你让 u8 变量加上超过 255,调试版会直接 panic(程序主动中止并报错),发布版则会绕回(wrap)到 0 重新数——两种都是明确的行为,而不是 C 里那种「看运气」。真需要绕回语义,就明说:x.wrapping_add(1)。又一次,Rust 的选择是:危险的事可以做,但必须写在脸上。

遮蔽:换个写法,不用 mut 也能「变」

Rust 还有个独特的小花招叫遮蔽(shadowing):用 let 再声明一次同名变量,旧的那个就被「遮住」了:

let spaces = "   ";        // spaces 是个字符串
let spaces = spaces.len(); // 同名,但现在是个数字 3

注意这和 mut 的区别:mut同一个变量原地改值,类型不许变;遮蔽是新声明一个变量,只是恰好同名,所以连类型都可以换。上面的代码如果改成 let mut spaces = " "; spaces = spaces.len(); 反而编译不过——字符串不能原地变成数字。

什么时候用哪个?同一个东西「原地改」,用 mut;一个值经过一步步加工、每步换个形态,用遮蔽,既保持了不可变的好处,又不用硬想出 spaces_strspaces_len 这类笨名字。

常量:连类型推断都不伺候

最后认一个亲戚:常量const 声明,必须写明类型,值必须是编译期就能算出来的东西:

const MAX_PLAYERS: u32 = 100;

常量和不可变变量的区别:不可变变量的值可以是运行时才确定的(比如读进来的文件长度),只是定下来之后不许改;常量的值在编译那一刻就烙死在程序里了。约定俗成,常量名全大写、单词间下划线。

这一章的三条小规矩——默认不可变、类型编译期确定、常量烙死——看起来都是写法上的洁癖。但它们有同一个指向:让编译器掌握尽可能多的确定性。编译器知道的越确定,它能替你担保的就越多。下一章我们迎接全书最重的一块石头:所有权。

Rust 入门:编译器是你最严格的朋友 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校