第3章 通用编程概念 (Common Concepts)

2026-09-07 00:00    #读书笔记  

本章涵盖 Rust 的基础语法:变量、类型、函数、注释、控制流。这些概念几乎所有语言都有,但 Rust 的实现有独特的规则。

3.1 变量与可变性

默认不可变

1let x = 5;
2x = 6;            // 编译错误:cannot assign twice to immutable variable

Rust 变量默认不可变(immutable)。设计理由:如果代码假设某个值不变却意外被修改,bug 很难追踪。编译器帮你杜绝这种情况。

mut 关键字

1let mut x = 5;     // mut 标记为可变
2x = 6;             // OK

mut 也是一份文档信号——告诉读代码的人这个值会变。

常量 const

1const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

let 的区别:

letconst
可变性默认不可变,可加 mut始终不可变
类型标注可推断必须标注
作用域任意作用域任意作用域,包括全局
运行时计算只能是编译期常量表达式
命名snake_caseSCREAMING_SNAKE_CASE

遮蔽 shadowing

1let x = 5;
2let x = x + 1;          // x = 6,遮蔽前一个 x
3
4{
5    let x = x * 2;      // x = 12,内部遮蔽
6    println!("{x}");    // 12
7}
8println!("{x}");        // 6 — 内部作用域结束,外层 x 恢复

关键点:重复 let 创建新变量并遮蔽同名旧变量。与 mut 的区别:

  1. let 重新绑定不允许意外赋值(不用 mutx = 6 会编译错误)。
  2. 可以变类型
1let spaces = "   ";       // &str
2let spaces = spaces.len(); // usize — 类型变了,OK
3
4let mut spaces = "   ";
5spaces = spaces.len();    // 编译错误:不能改变 mut 变量的类型

3.2 数据类型

Rust 是静态类型语言,编译期必须知道所有变量的类型。编译器通常能推断,但多义时需要显式标注:

1let guess: u32 = "42".parse().expect("Not a number");
2//        ^^^^ 不写会编译错误:type annotations needed

标量类型 scalar

单个值。4 种基本标量:整型、浮点型、布尔型、字符。

整数

长度有符号无符号
8i8u8
16i16u16
32i32u32
64i64u64
128i128u128
archisizeusize

整数字面量:

进制示例
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
字节(u8)b'A'
整型溢出

debug 模式:溢出会 panic。release 模式:溢出会回绕(wrapping),u8 256→0,257→1。如需显式处理:wrapping_addchecked_addoverflowing_addsaturating_add

浮点数

1let x = 2.0;      // f64,默认
2let y: f32 = 3.0; // f32

IEEE-754 标准,f32 单精度,f64 双精度。默认 f64(速度相近但精度更高)。

布尔

1let t = true;
2let f: bool = false;
条件必须是 bool

if number { ... } 非法。Rust 不会隐式转换非布尔值为布尔(不像 C/JS)。

字符 char

1let c = 'z';
2let z = 'ℤ';
3let cat = '😻';

用单引号(' 而非 "),4 字节,存 Unicode 标量值(不只是 ASCII)。

复合类型 compound

元组 tuple

1let tup: (i32, f64, u8) = (500, 6.4, 1);

固定长度,元素类型可以不同。两种访问方式:

1// 解构
2let (x, y, z) = tup;
3
4// 索引访问
5let five_hundred = tup.0;  // 从 0 开始
6let one = tup.2;

单元类型 () — 空元组,零个元素。不返回值的表达式默认返回 ()

数组 array

1let a = [1, 2, 3, 4, 5];
2let a: [i32; 5] = [1, 2, 3, 4, 5];  // 类型+长度
3let a = [3; 5];                        // [3, 3, 3, 3, 3]
1let first = a[0];  // 索引访问
越界检查

运行时越界会立刻 panic(不是返回垃圾值)。Rust 不允许无保护内存访问。

3.3 函数

命名约定:snake_case(全小写+下划线)。定义顺序无关,fn 定义即可。

1fn another_function(x: i32) {
2    println!("The value of x is: {}", x);
3}

语句 vs 表达式

语句 statement表达式 expression
执行操作,无返回值计算值
let y = 6;5 + 6 → 11
fn 定义函数调用
不能嵌套在赋值的右边可以
1let x = (let y = 6);  // 编译错误:let 是语句,不返回值

类似 C 中的 x = y = 6 在 Rust 不成立。

代码块 {} 是表达式:

1let y = {
2    let x = 3;
3    x + 1      // 注意:无分号!
4};
5// y = 4

结尾有分号 → 变成语句,返回 ()

返回值

1fn five() -> i32 {
2    5           // 无分号 = 表达式 = 返回值
3}
4
5fn plus_one(x: i32) -> i32 {
6    x + 1       // 无分号 = 返回 x+1
7}
1fn plus_one(x: i32) -> i32 {
2    x + 1;      // 有分号!变成语句,返回 (),与 -> i32 不匹配
3}               // 编译错误:mismatched types
分号决定一切

最后一行有 ; → 返回 ()。没 ; → 返回该表达式的值。这是初学者最容易搞混的地方。

3.4 注释

1// 单行注释
2
3// 多行注释
4// 每行都要写 //
5
6let x = 5; // 行尾注释也可以

文档注释 /////! 见第 14 章。

3.5 控制流

if 表达式

1if number < 5 {
2    println!("condition was true");
3} else if number % 3 == 0 {
4    println!("divisible by 3");
5} else {
6    println!("condition was false");
7}

if 是表达式,可在 let 右边使用:

1let number = if condition { 5 } else { 6 };

所有分支返回类型必须相同

1let number = if condition { 5 } else { "six" };
2// 编译错误:if and else have incompatible types

三种循环

循环用途
loop无限循环,手动 break
while条件满足时循环
for遍历集合/区间(最常用)

loop

1loop {
2    println!("again!");
3    // break;  // 才能退出
4}

从 loop 返回值

1let result = loop {
2    counter += 1;
3    if counter == 10 {
4        break counter * 2;  // break 后跟值 = 返回给 result
5    }
6};
7// result = 20

循环标签,用于嵌套循环:

1'counting_up: loop {            // 标签
2    loop {
3        break;                  // 退出内层循环
4        break 'counting_up;     // 退出外层循环
5    }
6}

while

1let mut number = 3;
2while number != 0 {
3    println!("{}!", number);
4    number -= 1;
5}

条件为 true 时执行循环体。

for(最推荐)

 1let a = [10, 20, 30, 40, 50];
 2
 3// 遍历数组
 4for element in a {
 5    println!("{element}");
 6}
 7
 8// 遍历区间
 9for number in (1..4).rev() {  // 3, 2, 1
10    println!("{number}");
11}
优先用 for

Ranges 1..4 — 含起点不含终点。1..=4 — 含两端。 rev() 反转区间。

for 是 Rust 中最常用的循环,没有索引边界错误,编译器也不需要插入运行时边界检查。用 while 遍历数组反而更危险且更慢。

练习

书中建议的 3 个练习(可以自己试试):

  1. 华氏度与摄氏度转换
  2. 生成 n 阶斐波那契数列
  3. 打印圣诞颂歌 “The Twelve Days of Christmas” 歌词