第 3 章:通用编程概念(Common Programming Concepts)
章节概要
本章涵盖变量与可变性、标量与复合数据类型、函数定义、注释规范以及控制流(if/loop/while/for)等通用编程概念。
本章图解

目录
- [3.1 变量与可变性(Variables and Mutability)](#3.1 变量与可变性(Variables and Mutability))
- [3.2 数据类型(Data Types)](#3.2 数据类型(Data Types))
- [3.3 函数(Functions)](#3.3 函数(Functions))
- [3.4 注释(Comments)](#3.4 注释(Comments))
- [3.5 控制流(Control Flow)](#3.5 控制流(Control Flow))
正文内容
Concepts)
本章涵盖了⼏乎在所有现代编程语⾔中都会出现的概念,以及它们在 Rust 中的具体⼯作⽅式。虽然这些概念本身
并⾮ Rust 所独有,但我们将⽴⾜于 Rust 的语境来讨论它们,并阐述围绕它们的使⽤惯例。
具体来说,本章将探讨:
- 变量与可变性(Variables and Mutability)
- 数据类型(Data Types)
- 函数(Functions)
- 注释(Comments)
- 控制流(Control Flow)
关键字(Keywords)
与其他语⾔类似,Rust 拥有保留给语⾔⾃身使⽤的关键字集合(例如 let、fn、mut 等)。你不能将这些保留字⽤
作变量名或函数名。
变量与可变性(Variables and Mutability)
正如在第 2 章提到的,在 Rust 中,变量默认是不可变的(immutable)。这是 Rust 给予你的众多提示之⼀,引
导你写出能充分利⽤ Rust 提供的安全性与简便并发优势的代码。当然,你依然可以选择将变量声明为可变的。
当⼀个变量是不可变的时,⼀旦⼀个值绑定到⼀个名字上,你就不能再修改该值。
为了直观体会这⼀点,使⽤ Cargo 新建⼀个名为 variables 的项⽬:
rust
cargo new variables BASH
cd variables
将 src/main.rs 的内容替换为以下代码(这段代码⽬前⽆法通过编译):
rust
// src/main.rs RUST
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
运⾏ cargo run,编译器会输出不可变错误信息:
rust
cargo run PLAINTEXT
Compiling variables v0.1.0 (file:///projects/variables)
errorE0384: cannot assign twice to immutable variable x
--> src/main.rs:4:5
|
rust
2 | let x = 5;
| - first assignment to x
rust
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
help: consider making this binding mutable
|
rust
2 | let mut x = 5;
| +++
这个例⼦展示了编译器如何协助你发现程序中的隐患。错误信息明确指出:不能对不可变变量 x 进⾏⼆次赋值。在
Rust 中,获得这类编译期错误⾄关重要,因为"意外修改本以为不会变的值"是许多隐蔽 Bug 的根源。Rust 编译器
保证:当你声明⼀个值不会变时,它就绝对不会改变,因此你⽆需⾃⼰在脑海中追踪它的状态变化。
但在某些场景下,可变性可以让代码编写更便利。只需在变量名前加上 mut 关键字即可将其声明为可变变量:
rust
// src/main.rs RUST
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
运⾏后成功输出:
The value of x is: 5 PLAINTEXT
The value of x is: 6
加上 mut 还向代码未来的阅读者清晰传达了意图:代码的其他部分将会改变这个变量的值。
常量(Declaring Constants)
类似于不可变变量,常量(constants)也是绑定到名称上且不允许修改的值。但常量与不可变变量有⼏点核⼼区
别:
-
常量不能使⽤
mut:常量不仅默认不可变,它们永远不可变。 -
声明⽅式与类型标注:声明常量使⽤
const关键字⽽⾮ let,并且必须显式标注类型。 -
作⽤域灵活:常量可以在任何作⽤域中声明,包括全局作⽤域,这使得它们⾮常适合在代码的多处共享使
⽤。
-
编译期求值:常量只能被设置为常量表达式(constant expression),⽽不能是只能在运⾏时计算得出的
值。
常量示例:
rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; RUST
Rust 约定常量的命名规范为全⼤写字⺟,单词之间使⽤下划线隔开。编译器可以在编译期直接计算表达式 60 * 60
rust
* 3 的结果(即 10800),这样书写既⽅便⼈类理解,也便于后期维护。常量在程序运⾏的整个⽣命周期内在其声
明的作⽤域中均有效。
变量遮蔽(Shadowing)
在第 2 章中你已经⻅识过:你可以声明⼀个与之前变量同名的新变量。Rustaceans 称之为第⼀个变量被第⼆个变
量遮蔽(shadowed)了。编译器在遇到该变量名时,看到的将是第⼆个新变量,直到它⾃身被再次遮蔽或其所在
的作⽤域结束:
rust
// src/main.rs RUST
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
运⾏后输出:
The value of x in the inner scope is: 12 PLAINTEXT
The value of x is: 6
在内部花括号创建的作⽤域中,第三个 let 语句再次遮蔽了 x,将其值变为 12。离开内部作⽤域后,内部的遮蔽结
束,外部的 x 恢复为 6。
遮蔽(Shadowing)与可变变量(mut)的区别
- 重新保持不可变:遮蔽机制通过重复使⽤ let 关键字创建了⼀个全新的变量。我们可以对⼀个值进⾏⼏次变
换计算,但在这些变换结束后,新变量依然保持不可变属性;如果我们忘记写 let ⽽直接给它赋值,编译器就
会报错保护我们。 - 可以改变变量类型:因为遮蔽实际上是创建了新变量,所以新变量可以拥有与旧变量不同的数据类型:
rust
let spaces = " "; // 字符串类型(&str) RUST
let spaces = spaces.len(); // 数字类型(usize)
如果试图⽤ mut 来实现这⼀点,则会报错:
rust
let mut spaces = " "; RUST
spaces = spaces.len(); // 编译报错!⽆法修改变量本身的类型
数据类型(Data Types)
Rust 中的每⼀个值都有其特定的数据类型。Rust 是⼀⻔静态类型语⾔(statically typed language),这意味着
它必须在编译期获知所有变量的类型。
通常,编译器能够根据值及其使⽤上下⽂⾃动推断类型。但在存在多种可能类型时(例如从字符串使⽤ .parse()
转换为数字),必须显式给出类型注解:
rust
let guess: u32 = "42".parse().expect("Not a number!"); RUST
如果省略 : u32,Rust 将⽆法确定解析为何种类型,编译报错。
Rust 的数据类型主要分为两⼤⼦集:标量类型(Scalar) 和 复合类型(Compound)。
标量类型(Scalar Types)
标量类型代表单⼀的值。Rust 有 4 种主要的标量类型:整数、浮点数、布尔值 和 字符。
- 整数类型(Integer Types)
整数是没有⼩数部分的数字。每个类型都有显式的⼤⼩,分为有符号(以 i 开头)和⽆符号(以 u 开头):
⻓度 有符号(Signed) ⽆符号(Unsigned)
8-bit i8 u8
16-bit i16 u16
32-bit i32(默认类型) u32
64-bit i64 u64
128-bit i128 u128
视系统架构⽽定 isize usize
-
有符号类型采⽤⼆进制补码(two's complement)表示,存储范围为 -(2^(n-1)) 到 2^(n-1) - 1。
-
⽆符号类型存储范围为 0 到 2^n - 1。
-
isize 和 usize 的⼤⼩取决于⽬标运⾏平台的架构:在 64 位系统上是 64 位,在 32 位系统上是 32 位。它
们的主要使⽤场景是对集合类型进⾏索引。
-
默认整数类型:Rust 的整型默认是
i32,它通常速度最快,即使在 64 位系统上也表现优异。整数字⾯量书写⽅式:
-
⼗进制:98_222(允许下划线 _ 作为视觉分隔符)
-
⼗六进制:0xff
-
⼋进制:0o77
-
⼆进制:0b1111_0000
-
字节(仅限 u8):b'A'
-
字⾯量还⽀持类型后缀,例如 57u8。
整数溢出(Integer Overflow)
假设⼀个 u8 变量(范围 0 到 255)被赋予 256:
-
在 Debug 模式 下编译时,Rust 包含溢出检查;⼀旦发⽣溢出,程序将在运⾏时 panic。
-
在 Release 模式(--release)下编译时,Rust 不包含导致 panic 的溢出检查。相反,它执⾏⼆进制补码回
绕(wrapping):256 变为 0,257 变为 1。依赖溢出回绕⾏为被视为⼀种错误。
-
若要显式处理可能的溢出,可以使⽤标准库提供的特定⽅法:wrapping_(回绕)、checked_ (发⽣溢出
返回 None)、overflowing_(返回结果及指示是否溢出的布尔值)和 saturating_ (饱和在最⼤/最⼩
值)。
- 浮点数类型(Floating-Point Types)
Rust 拥有两种浮点数原⽣类型,均遵循 IEEE-754 标准:
f32(32 位单精度)f64(64 位双精度,默认类型,因为在现代 CPU 上它的速度与 f32 相当,但精度更⾼)
所有的浮点类型都是有符号的。
rust
let x = 2.0; // f64 RUST
let y: f32 = 3.0; // f32
数值运算(Numeric Operations)
Rust ⽀持基本的四则数学运算和取余:+、-、*、/、%。
- 整数除法会向零截断(truncate toward zero)到最接近的整数(例如 -5 / 3 的结果是 -1)。
- 布尔类型(The Boolean Type)
布尔类型⽤bool表示,⼤⼩为 1 个字节,只有两个可能的值:true 和 false。它主要⽤于条件控制流(如 if 表
达式)。
rust
let t = true; RUST
let f: bool = false; // 带有显式类型注解
- 字符类型(The Character Type)
字符类型⽤char表示,使⽤单引号声明(字符串字⾯量使⽤双引号):
rust
let c = 'z'; RUST
let z: char = 'ℤ';
let heart_eyed_cat = '😻';
Rust 的 char 类型⼤⼩为 4 个字节,表示⼀个 Unicode 标量值(Unicode Scalar Value)。它可以表示远多于
ASCII 的字符,包括拼⾳重⾳字⺟、中⽂、⽇⽂、韩⽂字符、emoji 表情符号以及零宽空格等。其取值范围从
U+0000 到 U+D7FF,以及 U+E000 到 U+10FFFF。
复合类型(Compound Types)
复合类型可以将多个值组合为⼀个类型。Rust 原⽣提供了两种基础复合类型:元组(Tuple) 和 数组(Array)。
- 元组类型(The Tuple Type)
元组是将多个具有不同类型的值组合为⼀个复合类型的通⽤⽅式。元组具有固定⻓度:⼀旦声明,其⼤⼩不能增⻓
或缩减。
元组使⽤⼩括号包裹逗号分隔的值来创建:
rust
let tup: (i32, f64, u8) = (500, 6.4, 1); RUST
提取元组中的值有两种⽅式:
- 模式匹配解构(Destructuring):
rust
let tup = (500, 6.4, 1); RUST
let (x, y, z) = tup;
println!("The value of y is: {y}"); // 输出 6.4
- 点号索引访问(Dot notation):使⽤ . 后跟索引值(索引从 0 开始):
rust
let five_hundred = tup.0; RUST
let six_point_four = tup.1;
let one = tup.2;
单元类型(Unit Type):没有任何值的元组有⼀个特殊名称叫 unit(单元类型),其值和类型均写作 ()。它表示
空值或空的返回类型。如果表达式没有返回其他任何值,它将隐式返回单元值 ()。
- 数组类型(The Array Type)
数组也是多个值的集合,但与元组不同:
- 数组中的每⼀个元素必须是相同的类型。
- Rust 中的数组同样具有固定⻓度。
数组的值写在中括号内:
rust
let a = [1, 2, 3, 4, 5]; RUST
- 使⽤场景:当希望数据被分配在栈(stack)上⽽不是堆(heap)上,或者希望确保元素数量始终固定时,
数组⾮常有⽤(例如⼀年中 12 个⽉份的名称)。 - 与 Vector 的区别:标准库提供的 Vector(动态数组)允许在堆上动态伸缩;如果不确定该⽤数组还是
Vector,通常应该选择 Vector(将在第 8 章详述)。
数组类型注解与初始化语法: - 类型写法为 类型; ⻓度,例如 let a: i32; 5 = 1, 2, 3, 4, 5;。
- 初始化包含相同初始值的数组:let a = 3; 5;(等同于 let a = 3, 3, 3, 3, 3;)。
数组元素访问与越界检查:使⽤中括号及索引访问,例如 a0。
rust
let a = [1, 2, 3, 4, 5]; RUST
let first = a[0];
let second = a[1];
如果尝试访问越界的索引(例如 a10),Rust 会在运⾏时 panic:
index out of bounds: the len is 5 but the index is 10 PLAINTEXT
这是 Rust 内存安全原则 的体现。在许多底层语⾔(如 C)中,越界访问不会被检查,导致⾮法读取未分配或属于
其他数据的内存(缓冲区越界读 / buffer overread)。Rust 通过在运⾏时进⾏边界检查并⽴即终⽌程序来避免这
种内存安全漏洞。
函数(Functions)
在 Rust 中,函数⽆处不在。使⽤ fn 关键字声明函数。
Rust 代码约定使⽤ 蛇形命名法(snake case) 作为函数与变量名的规范⻛格:所有字⺟均⼩写,单词之间⽤下划
线分隔。
rust
// src/main.rs RUST
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Rust 不在乎你在哪⾥定义函数,只要在调⽤者能看得⻅的作⽤域内定义了它即可(定义在 main 之前或之后都可
以)。
函数参数(Parameters)
函数可以定义包含参数。在函数签名中,必须显式声明每个参数的类型。这是 Rust 设计中的刻意决定:强制参数
类型注解意味着编译器⼏乎⽆需在其他地⽅推测函数意图,并且能在发⽣不匹配时提供极其精准的错误提示。
多个参数之间使⽤逗号隔开:
rust
fn main() { RUST
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
语句与表达式(Statements and Expressions)
Rust 是⼀⻔基于表达式(expression-based)的语⾔,这是⼀个极其关键的概念区别:
- 语句(Statements):是执⾏某种动作并不返回值的指令。
- 表达式(Expressions):进⾏计算并产⽣⼀个结果值。
- 变量绑定 let y = 6; 是⼀条语句。因为语句不返回值,所以不能将 let 语句赋值给另⼀个变量(在 C 或
Ruby 中可以写 x = y = 6,但在 Rust 中不⾏):
rust
let x = (let y = 6); // 编译报错! RUST
- 表达式会计算出⼀个值。5 + 6 是⼀个求值为 11 的表达式。函数调⽤是表达式,宏调⽤是表达式,花括号创
建的代码块 {} 也是表达式:
rust
let y = { RUST
let x = 3;
x + 1 // 注意:结尾没有分号!
rust
};
println!("The value of y is: {y}"); // y 的值是 4
注意:表达式末尾没有分号! 如果你在表达式末尾加上了分号,它就变成了语句,语句不会返回值。
具有返回值的函数(Functions with Return Values)
函数可以向调⽤者返回值。在函数签名中,我们在箭头 -> 后声明返回值的类型。
在 Rust 中,函数的返回值等同于其函数体代码块中最后⼀个表达式的值。虽然可以使⽤ return 关键字显式提前
返回,但⼤多数惯⽤的 Rust 代码都是隐式返回最后⼀个表达式:
rust
fn five() -> i32 { RUST
5 // 这是⼀个表达式,作为返回值;不要加分号!
rust
}
fn main() {
let x = five();
println!("The value of x is: {x}"); // 输出 5
rust
}
再看⼀个示例:
rust
fn plus_one(x: i32) -> i32 { RUST
x + 1
rust
}
如果你在 x + 1 后⾯不⼩⼼加了⼀个分号 ;:
rust
fn plus_one(x: i32) -> i32 { RUST
x + 1; // 变成了⼀条语句!
rust
}
编译器会抛出类型不匹配错误:
errorE0308: mismatched types PLAINTEXT
--> src/main.rs:7:24
|
rust
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected i32, found ()
| |
| implicitly returns () as its body has no tail or return expression
rust
8 | x + 1;
| - help: remove this semicolon to return this value
因为语句不产⽣值,代码块隐式返回了单元类型 (),与函数签名承诺的 i32 发⽣冲突,编译器会贴⼼地建议你移
除分号。
注释(Comments)
在 Rust 中,惯⽤的单⾏注释以两条正斜杠 // 开始,并⼀直持续到⾏尾:
rust
// 这是⼀个独⽴的注释⾏ RUST
let lucky_number = 7; // 也可以位于代码⾏末尾
对于跨越多⾏的注释,每⾏均以 // 开头:
rust
// 这是⼀段较为复杂的逻辑, RUST
// 我们通过多⾏注释来详细解释
// 具体的实现思路。
此外,Rust 还提供⽂档注释(以 /// 开头),这将在第 14 章详细讨论。
控制流(Control Flow)
控制代码执⾏流向的最常⻅结构是 if 表达式 和 循环(loops)。
- if 表达式
rust
if 表达式允许根据条件分⽀执⾏代码:
// src/main.rs RUST
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
- 条件必须是
bool类型:与 JavaScript 或 Python 等语⾔不同,Rust 不会⾃动尝试将⾮布尔类型转换为布
尔值。如果写 if number {,编译器会直接报错 expected bool, found integer。必须显式提供布尔条件,例
如 if number != 0 {。
使⽤ else if 处理多重条件
rust
fn main() { RUST
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
程序按顺序检查各个条件,⼀旦发现第⼀个为 true 的分⽀,就会执⾏对应分⽀的代码并跳出判断,不会再去检查
后续的分⽀(即使 6 同样能被 2 整除)。如果 else if 过多,通常应重构为第 6 章的 match 结构。
在 let 语句中使⽤ if
因为 if 是⼀个表达式,所以可以直接将其放在 let 语句等号右侧来为变量赋值:
rust
fn main() { RUST
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}"); // 输出 5
rust
}
• 所有分⽀的返回类型必须相同:变量必须有唯⼀定义的类型。如果写 let number = if condition { 5 }
else { "six" };,编译将会失败,因为 if 臂和 else 臂的返回类型不兼容(integer 与 &str)。
- 循环(Repetition with Loops)
Rust 提供了 3 种循环结构:loop、while 和 for。
rust
loop 循环
loop 关键字指示 Rust ⼀遍⼜⼀遍地反复执⾏代码块,直到遇到中断或显式终⽌:
fn main() { RUST
loop {
println!("again!");
}
}
终端可以通过快捷键 Ctrl-C 终⽌死循环。代码中可以通过 break 退出循环,或通过 continue 跳过当前迭代直
接进⼊下⼀次循环。
从循环中返回值
rust
loop 常⽤于需要重试可能失败的操作(如轮询线程状态)。你可以将需要传递给外部的结果值放在 break 之后返
回:
rust
fn main() { RUST
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 返回 20
}
};
println!("The result is {result}"); // 输出 20
rust
}
循环标签(Loop Labels)消除多层嵌套歧义
如果有嵌套的多层循环,break 和 continue 默认只作⽤于最内层循环。你可以在循环前指定以单引号开头的循环
标签(loop label),并结合 break/continue 精确跳出指定的外层循环:
rust
fn main() { RUST
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break; // 仅退出内层循环
rust
}
if count == 2 {
break 'counting_up; // 退出外层 labeled 循环
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}
while 条件循环
当需要在循环内判断条件时,while 循环是内置的语法糖,避免⼿动组合 loop、if、else 和 break:
rust
fn main() { RUST
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
for 循环遍历集合
虽然可以使⽤ while 结合下标索引来遍历数组或集合,但这种做法容易出错(例如索引写错导致越界 panic),且
运⾏时每次迭代都要做越界检查因此较慢。
Rust 中更安全、简洁、⾼效且最常⽤的⽅式是 for 循环:
rust
fn main() { RUST
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
使⽤ for 循环彻底杜绝了越界或遗漏元素的可能,同时编译⽣成的机器码效率更⾼。
如果要实现指定次数的循环(例如倒计时),可以配合标准库的 Range(范围)及其 .rev()(反转)⽅法:
rust
fn main() { RUST
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
(1...4) ⽣成 1、2、3(前闭后开区间),.rev() 将其倒转为 3、2、1,代码清晰且优雅。
本章⼩结(Summary)
本章内容⾮常充实!你系统掌握了:
- 变量与可变性:默认不可变、mut 声明可变、const 常量以及通过 shadowing 优雅转换数据。
- 基本数据类型:
- 标量类型:整数(i32、溢出机制)、浮点数(f64)、布尔型(bool)、字符(char,4 字节 Unicode)。
- 复合类型:固定⻓度与异构的元组(tuple)、固定⻓度与同构的数组(array 及其越界保护)。
- 函数与语义:参数强类型注解,深刻理解语句(不返回值)与表达式(返回值)的核⼼差异,以及函数末尾
⽆分号的隐式返回特性。
- 控制流:强布尔类型的 if 表达式(可赋值给 let),以及 loop(可带返回值与标签)、while 和最惯⽤的
rust
for 集合/区间遍历。
练习建议:尝试⾃⼰动⼿写⼏个⼩程序巩固这些知识:
-
摄⽒度与华⽒度温度互相转换。
-
⽣成第 n 个斐波那契数。
-
利⽤循环和重复特性打印圣诞颂歌《The Twelve Days of Christmas》的歌词。
准备好后,我们将在第 4 章迈⼊ Rust 独⼀⽆⼆的核⼼基⽯------所有权(Ownership)机制!
FF!!!");
}(1..4) ⽣成 1、2、3(前闭后开区间),.rev() 将其倒转为 3、2、1,代码清晰且优雅。
本章⼩结(Summary)
本章内容⾮常充实!你系统掌握了:- 变量与可变性:默认不可变、mut 声明可变、const 常量以及通过 shadowing 优雅转换数据。
- 基本数据类型:
- 标量类型:整数(i32、溢出机制)、浮点数(f64)、布尔型(bool)、字符(char,4 字节 Unicode)。
- 复合类型:固定⻓度与异构的元组(tuple)、固定⻓度与同构的数组(array 及其越界保护)。
- 函数与语义:参数强类型注解,深刻理解语句(不返回值)与表达式(返回值)的核⼼差异,以及函数末尾
⽆分号的隐式返回特性。
4. 控制流:强布尔类型的 if 表达式(可赋值给 let),以及 loop(可带返回值与标签)、while 和最惯⽤的rustfor 集合/区间遍历。
练习建议:尝试⾃⼰动⼿写⼏个⼩程序巩固这些知识:
- 摄⽒度与华⽒度温度互相转换。
- ⽣成第 n 个斐波那契数。
- 利⽤循环和重复特性打印圣诞颂歌《The Twelve Days of Christmas》的歌词。
准备好后,我们将在第 4 章迈⼊ Rust 独⼀⽆⼆的核⼼基⽯------所有权(Ownership)机制!