Rust 学习笔记(3/21):通用编程概念——把地基打牢
本系列基于官方《Rust 程序设计语言》(The Rust Programming Language,TRPL)逐章学习。
开篇:盖楼先打地基
前两章我们”跑”通了猜数字游戏,但对很多语法只有模糊印象。第 3 章是全书的地基工程:变量、数据类型、函数、注释、控制流——这些概念几乎所有语言都有,但 Rust 在每个点上都有自己的”脾气”。地基打得牢不牢,直接决定第 4 章所有权这座”大厦”能不能盖起来。
变量与可变性:Rust 的第一个”脾气”
变量默认不可变。试着给 let x = 5; 再赋值,编译器立刻亮红牌:
1
2
error[E0384]: cannot assign twice to immutable variable `x`
help: consider making this binding mutable: `mut x`
这不是找茬:如果代码一部分假设某值永不变、另一部分偷偷改了它,bug 极难追踪。编译器帮你保证”说好不变就不变”。需要可变时加 mut:
1
2
let mut x = 5; // 可变
x = 6;
与之容易混淆的三样东西,用一张表分清:
| 形式 | 关键字 | 可变性 | 特点 |
|---|---|---|---|
| 变量(默认) | let |
不可变 | 可加 mut 变可变 |
| 常量 | const |
永不可变 | 必须标注类型,可声明在任意作用域(含全局),值必须是编译期可计算的表达式(如 60 * 60 * 3),命名全大写+下划线 |
| 遮蔽 | let 复用同名 |
新绑定 | 用新值”盖住”旧值,允许改变类型(let spaces = " "; let spaces = spaces.len();),转换完成后变量再次不可变 |
遮蔽与 mut 的核心区别:mut 改变值时不能改变类型(let mut spaces = " "; spaces = spaces.len(); 会报 E0308 类型不匹配),而遮蔽本质是创建新变量,所以类型随便换。
数据类型:静态类型的约束与便利
Rust 是静态类型语言,编译期必须知道所有变量类型。编译器通常能靠值和用法推断,但 parse 这类”多义”场景必须显式标注(let guess: u32 = "42".parse()...,否则报 E0282)。
标量类型(单个值)有四种:
| 类型 | 说明 | 要点 |
|---|---|---|
| 整型 | i8~i128、u8~u128、isize/usize |
默认 i32;isize/usize 随系统架构定,常用于集合索引;字面量可用 _ 分隔(1_000)、前缀(0xff) |
| 浮点 | f32、f64 |
默认 f64(现代 CPU 上几乎与 f32 同速但精度更高);整数除法向下取整(2 / 3 得 0) |
| 布尔 | bool |
1 字节,true/false |
| 字符 | char |
4 字节,Unicode 标量值——emoji、中文都算,用单引号 |
整型溢出:debug 模式直接 panic;release 模式做二进制补码”包裹”(u8 的 256 变 0)。要显式处理可用
wrapping_*/checked_*/overflowing_*/saturating_*系列方法。
复合类型(多个值)有两种:
- 元组(tuple):长度固定、元素类型可以不同。
let tup: (i32, f64, u8) = (500, 6.4, 1);可用模式匹配解构let (x, y, z) = tup;或用索引tup.0访问;()是特殊”单元类型”,不返回值时隐式返回它; - 数组(array):长度固定、元素类型必须相同。
let a: [i32; 5] = [1, 2, 3, 4, 5];简写let a = [3; 5];(5 个 3)。越界访问会在运行时 panic——这是 Rust 安全原则的第一个体现:宁可崩溃退出,也不访问无效内存。需要可增删的集合用 vector(第 8 章)。
函数:参数、语句与表达式
函数名用 snake_case(全小写+下划线),定义位置无关紧要(在 main 之前之后都行)。签名里必须标注每个参数类型(fn another_function(x: i32))。
Rust 是基于表达式的语言,这是理解函数的关键:
- 语句(statement):执行操作但不返回值,如
let y = 6;; - 表达式(expression):计算并产生值,如
5 + 6、函数调用、代码块{}。表达式末尾没有分号,加分号就变成语句、不再返回值。
因此函数返回值的写法很特别:函数体最后一个表达式就是返回值,类型写在 -> 后:
1
2
3
4
5
6
7
fn five() -> i32 {
5 // 没有分号!这是返回值
}
fn plus_one(x: i32) -> i32 {
x + 1 // 若写成 x + 1; 则报 E0308:expected i32, found ()
}
代码块也是表达式:let y = { let x = 3; x + 1 }; 中 y 得到 4。
控制流:if 与三种循环
if 的条件必须是 bool——Rust 不会像 Ruby/JavaScript 那样做真假值隐式转换,if number(number 是整数)会直接报错,必须写 if number != 0。else if 会按顺序找第一个为真的分支执行。因为 if 是表达式,可以直接用于赋值:
1
let number = if condition { 5 } else { 6 }; // 两分支类型必须一致,否则报 E0308
三种循环的取舍:
| 循环 | 适用场景 | 关键点 |
|---|---|---|
loop |
无条件重复,直到显式停止 | break 跳出(可带返回值 break counter * 2);continue 跳过本轮;嵌套时可用循环标签 'label: loop { ... break 'label; } 跳出外层 |
while |
条件循环 | while number != 0 { ... },比手写 loop+if+break 简洁 |
for |
遍历集合(Rust 使用最多) | for element in a 无需管理索引、天然防越界;配合 Range 做定次循环:for number in (1..4).rev() |
用 while 加索引遍历数组易错(数组改长度忘了改条件就 panic),for 则完全没有这个烦恼——这正是它成为 Rust 首选循环的原因。
实践建议与总结
- 把”默认不可变”当默认姿势:先写不可变,编译器喊痛再加
mut,这是 Rust 帮你防 bug 的第一道防线; - 善用类型错误信息:本章出现的 E0384/E0308/E0282 都是”老师”,错误信息会直接给出修复建议(如”考虑加 mut”“删除分号”);
- 优先
for而非while+索引:遍历集合永远选for,简洁、安全、更符合 Rust 惯例。
本章的收获可以浓缩成一张”地基清单”:变量(默认不可变/常量/遮蔽)、类型(四种标量+两种复合)、函数(参数必标注/表达式即返回值)、注释(//)、控制流(if 必 bool/三种循环选 for)。地基打好,下一章就来啃全书最硬的核心——所有权(ownership),那是 Rust 与其他语言最不同的地方。