本章涵盖 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 的区别:
let | const | |
|---|---|---|
| 可变性 | 默认不可变,可加 mut | 始终不可变 |
| 类型标注 | 可推断 | 必须标注 |
| 作用域 | 任意作用域 | 任意作用域,包括全局 |
| 值 | 运行时计算 | 只能是编译期常量表达式 |
| 命名 | snake_case | SCREAMING_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 的区别:
let重新绑定不允许意外赋值(不用mut写x = 6会编译错误)。- 可以变类型:
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 种基本标量:整型、浮点型、布尔型、字符。
整数
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8 | i8 | u8 |
| 16 | i16 | u16 |
| 32 | i32 | u32 |
| 64 | i64 | u64 |
| 128 | i128 | u128 |
| arch | isize | usize |
- 默认整数类型:
i32 isize/usize用于集合索引(64 位平台上即 64 位)
整数字面量:
| 进制 | 示例 |
|---|---|
| 十进制 | 98_222 |
| 十六进制 | 0xff |
| 八进制 | 0o77 |
| 二进制 | 0b1111_0000 |
| 字节(u8) | b'A' |
debug 模式:溢出会 panic。release 模式:溢出会回绕(wrapping),u8 256→0,257→1。如需显式处理:wrapping_add、checked_add、overflowing_add、saturating_add。
浮点数
1let x = 2.0; // f64,默认
2let y: f32 = 3.0; // f32
IEEE-754 标准,f32 单精度,f64 双精度。默认 f64(速度相近但精度更高)。
布尔
1let t = true;
2let f: bool = false;
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]
- 固定长度,元素必须同类型。
- 存在栈上。元素数确定时用数组,否则用
Vec(第 8 章)。
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}
- 用
-> type声明返回类型。 - 隐式返回:函数体最后一个表达式的值。
- 显式返回:
return value;(提前退出时使用)。
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}
- 条件必须是
bool,不会隐式转换。 - 只执行第一个匹配分支,不 fall-through。
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}
Ranges 1..4 — 含起点不含终点。1..=4 — 含两端。
rev() 反转区间。
for 是 Rust 中最常用的循环,没有索引边界错误,编译器也不需要插入运行时边界检查。用 while 遍历数组反而更危险且更慢。
练习
书中建议的 3 个练习(可以自己试试):
- 华氏度与摄氏度转换
- 生成 n 阶斐波那契数列
- 打印圣诞颂歌 “The Twelve Days of Christmas” 歌词