一句话总结
Rust 变量默认不可变,要改得显式 mut;整数必须明确位宽且溢出会被检查;类型转换不会隐式发生,一律显式 as 或 From.
这三点处处体现 Rust "显式优于隐式"的设计哲学,也是 Go 程序员最先要适应的地方.
变量默认不可变
第一个反直觉设定:let 声明的变量默认不可变(immutable),赋值后不能再改.
|
Go 里变量天然可变,要靠 const 才能锁定;Rust 正好相反--默认锁定,要改才声明 mut.这不是为难--它让"这个值会不会变"成为代码里一眼可见的信息,也和后面所有权,借用规则环环相扣.
最佳实践:能不可变就不可变
默认写 let,只在确实需要修改时才加 mut.这能让读代码的人(包括未来的自己)立刻知道哪些状态是稳定的.
Clippy 还会主动提示"声明了 mut 却从未修改"的多余可变性.
变量遮蔽(Shadowing)
Rust 允许用同名 let 重新声明,覆盖前一个变量--这叫遮蔽,它甚至能改变类型:
|
遮蔽与 mut 是两回事,别混淆:
mut 修改 |
遮蔽(shadowing) | |
|---|---|---|
| 本质 | 改同一个变量的值 | 用新变量覆盖旧名字 |
| 能否换类型 | 不能 | 能 |
| 典型用途 | 循环计数,状态累积 | 解析转换后复用同名,如 str → 数值 |
遮蔽像给旧名字"换了个人顶岗":名字没变,但背后是全新的变量(可以是不同类型).
mut 则是同一个人换了身衣服,人(类型)还是那个人.
const:编译期常量
不可变的 let 是运行期绑定;真正的"常量"用 const,它在编译期确定,必须标注类型,且只能用常量表达式初始化:
|
const 才更接近 Go 的 const.普通的 let x = 5 只是"不会变的运行期变量",二者不同.
标量类型:整数必须标位宽
Rust 有四类标量:整数,浮点,布尔,字符.和 Go 最大的不同是整数类型必须明确位宽和符号.
| 类别 | Rust | Go 对应 | 说明 |
|---|---|---|---|
| 有符号整数 | i8 i16 i32 i64 i128 isize |
int8 ... int64 int |
未标注时默认推断 i32 |
| 无符号整数 | u8 u16 u32 u64 u128 usize |
uint8 ... uint |
集合索引/长度用 usize |
| 浮点 | f32 f64 |
float32 float64 |
默认推断 f64 |
| 布尔 | bool |
bool |
一致 |
| 字符 | char |
rune |
4 字节 Unicode 标量值 |
|
usize:索引就用它
给集合做下标,表示长度时,类型是 usize(其位宽随平台,64 位机上是 64 位).
这是高频类型,记住"凡是和'多少个,第几个'相关的,基本都是 usize".
整数溢出:Rust 会管,Go 不管
一个重要的行为差异.Go 整数溢出静默回绕(255u8 + 1 == 0,不报错);Rust 则区分构建模式:
- debug 构建:溢出直接
panic,帮助当场抓到 bug. - release 构建:溢出回绕(出于性能),行为同 Go.
真正想表达某种溢出语义时,用明确的方法,而不是依赖默认行为:
|
最佳实践:用语义明确的运算方法
处理可能溢出的计算(如累加用户输入,做容量计算)时,别裸用 +,而要根据需求选 checked_*(要安全),saturating_*(要封顶)或 wrapping_*(要回绕).
把意图写进代码,比依赖"debug 会 panic"可靠得多.
类型转换:没有隐式转换
Rust 绝不隐式转换数值类型--连 i32 和 i64 相加都不行.这和 Go 一致(Go 也要求显式转换),但 Rust 更严格,手段也更明确:
|
as 转换会截断,要当心
as 在大类型转小类型时会静默截断(300i32 as u8 == 44),不报错也不 panic.
涉及可能丢失精度/范围的转换,优先用 try_from--它返回 Result,溢出时给出 Err 而非默默截断.as 留给确定安全的场景(如小转大,与底层 API 交互).From/TryFrom 的完整机制在第 14 篇错误处理还会再遇到.
char 不是字节
Rust 的 char 占 4 字节,表示一个 Unicode 标量值,和 Go 的 rune 一致;但要注意它不等于字符串里的字节.Rust 干脆禁止用整数下标取字符串字符,逼开发者明确"按字节"还是"按字符":
|
字符串细节留到后面
Rust 字符串有 String 与 &str 之分,还有 UTF-8 编码带来的种种细节,本系列第 08 篇会专门讲集合时展开.
这里只需记住:char 是字符,不是字节,字符串不能整数下标.
元组与单元类型
顺带认识两个会频繁出现的类型.元组(tuple) 把多个值组合在一起,常用于函数返回多值:
|
单元类型 ()(unit)是"没有有意义的值"的类型,只有一个值 ().没有显式返回值的函数,返回的就是它--相当于其他语言的 void.
快速回顾
- 默认不可变:
let不可变,mut才可变;最佳实践是能不可变就不可变. - 遮蔽:同名
let创建新变量,可换类型,区别于mut改值;const是编译期常量. - 整数标位宽:默认
i32/f64;索引/长度用usize. - 溢出:debug 下 panic,release 回绕;按需用
checked_/saturating_/wrapping_方法表达意图. - 无隐式转换:用
as(简单但会截断)或From/TryFrom(安全);有损转换优先try_from. - char 不等于字节:char 是 4 字节 Unicode 标量,字符串不能整数下标;元组组合多值,
()是 void.
动手练习
- 不可变与遮蔽:声明不可变变量并尝试修改,观察报错,再用
mut修复;然后用 shadowing 把一个字符串变量重新绑定为它的长度. - 溢出方法对比:对
let x: u8 = 250分别调用checked_add(10),saturating_add(10),wrapping_add(10),打印并解释三者结果差异. - 转换安全性:用
u8::try_from(300)观察返回的Err,再用300i32 as u8对比截断结果. - 多返回值:写一个返回
(i32, i32, f64)的函数,返回一组数的最小值,最大值,平均值,并在调用处解构接收. - UTF-8 验证:对一个含非 ASCII 字符的字符串,分别打印它的
.len()(字节数)和.chars().count()(字符数),解释为何不同.