4. 流程控制
4.1 简介
在现实生活中,人们需要根据不同情境作出判断与决策。软件作为现实世界在计算机中的投影,映射到计算机世界,便体现为对各种数据进行判断与处理。同样,现实中存在大量重复性工作,需要反复执行相应的操作,映射到计算机中,则表现为对同类数据进行重复处理。因此,流程控制语句的核心可以归纳为两点:条件判断与重复执行。
Rust 支持的条件判断语句有 if、if-let、let-else 和 match,支持的重复执行语句有 loop、while、while-let 和 for。
4.2 if 语句
if 语句用于判断条件是否成立,若成立则执行相应代码块。例如,当温度超过 35 度时,打印输出高温提示。
fn main() {
let temperature = 36;
if temperature > 35 {
println!("高温提示:温度超过 35 度!");
}
}
shell> cargo run
高温提示:温度超过 35 度!
此外,if 还可配合 else if 与 else 使用,其中 else if 用于实现多重条件判断,else 用于处理所有条件不成立的情况。
fn main() {
let temperature = 36;
if temperature > 35 {
println!("高温警告:温度超过 35 度!");
} else if temperature > 18 {
println!("温暖天气:温度在 18 至 35 度之间。");
} else {
println!("寒冷天气:温度低于 18 度。");
}
}
4.3 if-let 语句
if let 语句用于判断某个值是否匹配指定模式,若匹配成功,则执行相应代码块,并可在判断的同时将枚举、数组、元组或结构体中的值解构为变量直接使用。
#[allow(dead_code)]
enum Option {
Some(i32),
None,
}
fn main() {
let month = 8;
// 多个值匹配
if let 8 | 9 = month {
println!("多个值匹配:八月或九月");
}
// 范围匹配
if let 6..=12 = month {
println!("范围匹配:下半年");
}
// 数组
let arr = [1, 2, 3];
if let [1, x, 3] = arr {
println!("数组解构:中间元素 = {}", x);
}
// 元组
let tup = (0, 5);
if let (0, y) = tup {
println!("元组解构:y = {}", y);
}
// 结构体
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 8 };
if let Point { x: 0, y } = p {
println!("结构体解构:y = {}", y);
}
// 枚举
let opt = Option::Some(10);
if let Option::Some(v) = opt {
println!("枚举解构:v = {}", v);
}
}
shell> cargo run
多个值匹配:八月或九月
范围匹配:下半年
数组解构:中间元素 = 2
元组解构:y = 5
结构体解构:y = 8
枚举解构:v = 10
此外,if 还可配合 else if let 与 else 使用,其中 else if let 用于对多个模式依次进行匹配判断,else 用于处理所有模式匹配失败时的情况。
fn main() {
let array = [10, 20, 30];
if let [20, _b, _c] = array {
println!("匹配到 [20, b, c]");
} else if let [10, _b, _c] = array {
println!("匹配到 [10, b, c]");
} else {
println!("没有匹配到");
}
}
shell> cargo run
匹配到 [10, b, c]
4.4 let-else 语句
let-else 语句用于将值与指定模式进行匹配,若匹配失败,则立即进入 else 分支执行相应代码,常用于处理错误或异常情况。此外,它也支持在匹配成功时将值解构为变量直接使用。
#[allow(dead_code)]
enum Result {
Ok(f64),
Err(String),
}
fn main() {
let a = 100;
let b = 0;
// 根据除数是否为 0,构造不同的结果
let result = if b == 0 {
Err("除数不能为 0".to_string())
} else {
Ok(a as f64 / b as f64)
};
// 使用 let-else 进行模式匹配:
// 若匹配到 Ok(div),则继续执行;
// 若匹配失败(即为 Err),则进入 else 分支并提前返回
let Ok(div) = result else {
eprintln!("计算结果异常");
return;
};
// 只有在成功得到除法结果时,才会执行到这里
println!("div = {div}");
}
shell> cargo run
计算结果异常
4.5 match 语句
match 语句与 if let 类似,均用于判断某个值是否匹配指定模式。与 if let 不同的是,match 要求覆盖所有可能的模式,且可返回值,各分支的返回值类型必须一致。
#[allow(dead_code)]
enum Option {
Some(i32),
None,
}
fn main() {
let month = 8;
// match 语句必须覆盖所有可能的模式
match month {
8 | 9 => println!("多个值匹配:八月或九月"),
v => println!("其他值:{v}"),
}
// match 语句可以返回值
let month_str = match month {
1..6 => "上半年",
6..=12 => "下半年",
_ => "数据异常,不合法的月份" // 无需要使用可以使用下划线省略变量名
};
println!("月份:{month_str}");
// 数组
let arr = [1, 2, 3];
match arr {
[1, x, 3] => println!("数组解构:中间元素 = {x}"),
_ => {}
}
// 元组
let tup = (0, 5);
match tup {
(0, y) => println!("元组解构:y = {y}"),
_ => {}
}
// 结构体
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 8 };
match p {
Point { x: 0, y } => println!("结构体解构:y = {y}"),
_ => {}
}
// 枚举
let opt = Option::Some(10);
match opt {
Option::Some(v) => println!("枚举解构:v = {v}"),
Option::None => {}
}
}
shell> cargo run
多个值匹配:八月或九月
月份:下半年
数组解构:中间元素 = 2
元组解构:y = 5
结构体解构:y = 8
枚举解构:v = 10
此外,match 语句还支持条件守卫(guard):在模式匹配成功后,可进一步通过附加条件筛选匹配结果,从而使 match 具备更精细的条件判断能力。
fn main() {
let number = 5;
let result = match number {
x if x % 2 == 0 => "偶数", // 条件守卫:只有当 x > 0 时才匹配
x if x % 2 == 1 => "奇数",
_ => "未知",
};
println!("result = {result}");
}
shell> cargo run
result = 奇数
4.6 loop 循环
loop 循环用于重复执行特定代码块,在循环体内,可以使用 continue 关键字跳过当前循环的剩余部分,直接进入下一次循环;而 break 关键字则用于跳出循环,并且还可以返回一个值(可选)。
fn main() {
let mut number = 0;
let last_number = loop {
number += 1;
// 跳过所有偶数
if number % 2 == 0 {
continue;
}
// 当 number > 10 时,跳出循环
if number > 10 {
break number;
}
println!("number = {number}");
};
println!("last_number = {last_number}");
}
shell> cargo run
number = 1
number = 3
number = 5
number = 7
number = 9
last_number = 11
continue 和 break 关键字默认只对当前循环有效。当循环嵌套时,可以通过指定循环标签,使 break 和 continue 作用于指定循环。
fn main() {
let mut number = 0;
// 为循环设置循环标签,格式为 `'标签名:`
'outside: loop {
number += 5;
loop {
number += 1;
// 跳过所有偶数
if number % 2 == 0 {
continue;
}
// 当 number > 10 时,跳出循环
if number > 10 {
break 'outside; // 让 break 作用于标记的循环, 而不是当前循环
}
println!("number = {number}");
};
}
}
shell> cargo run
number = 7
number = 9
2.5 while 循环
while 循环在每次迭代前都会先判断条件是否成立,仅当条件成立时才执行循环体。与 loop 相同,while 同样支持 break 和 continue 关键字。
fn main() {
let mut index = 0;
// 每次循环执行前都会判断 index 是否小于 10
while index < 10 {
println!("index: {index}");
index += 1;
}
}
shell> cargo run
index: 0
index: 1
index: 2
index: 3
index: 4
index: 5
index: 6
index: 7
index: 8
index: 9
2.6 while-let 循环
while let 循环在每次迭代前都会先判断模式是否匹配,仅当匹配成功时才执行循环体。与 while 相同,该语句同样支持 break 和 continue 关键字。
enum Status {
InProgress(usize),
Completed,
}
fn main() {
let mut current = Status::InProgress(0);
// 在进入每一轮循环前,都会先判断还否还在 InProgress 状态
while let Status::InProgress(progress) = current {
let new_progress = progress + 1;
current = Status::InProgress(new_progress);
println!("Progress: {new_progress}%");
if new_progress == 10 {
current = Status::Completed;
}
}
}
shell> cargo run
Progress: 1%
Progress: 2%
Progress: 3%
Progress: 4%
Progress: 5%
Progress: 6%
Progress: 7%
Progress: 8%
Progress: 9%
Progress: 10%
2.8 for 循环
for 循环从迭代器中依次取出元素,每次成功取到元素后,将其绑定到指定变量并执行一次循环体;当迭代器耗尽时,循环自动结束。与 while 相同,for 同样支持 break 和 continue 关键字。迭代器是一种统一“不断获取下一个值“的机制,使程序无需关注数据源(数组、切片、文件、网络流等)的具体差异,即可实现统一遍历。Rust 中的数组、切片等数据结构均支持迭代器遍历。
Rust 循环的语法形式为 for 模式 in 迭代器 { ... },用于从迭代器中依次取出元素并执行循环体。
fn main() {
let mut arr = [10, 20, 30];
for x in arr {
println!("遍历数组: x = {x}");
}
for x in &arr {
println!("遍历数组(按引用): x = {x}");
}
for x in &mut arr {
*x *= 10;
println!("遍历数组并修改: x = {x}");
}
for (index, value) in arr.iter().enumerate() {
println!("遍历数组(带索引): {index}: {value}");
}
for i in 0..5 {
println!("遍历范围: i = {i}");
}
// 解构模式绑定
let pairs = [(1, 2), (3, 4)];
for (a, b) in pairs {
println!("解构元组: a = {a}, b = {b}");
}
}
shell> cargo run
遍历数组: x = 10
遍历数组: x = 20
遍历数组: x = 30
遍历数组(按引用): x = 10
遍历数组(按引用): x = 20
遍历数组(按引用): x = 30
遍历数组并修改: x = 100
遍历数组并修改: x = 200
遍历数组并修改: x = 300
遍历数组(带索引): 0: 100
遍历数组(带索引): 1: 200
遍历数组(带索引): 2: 300
遍历范围: i = 0
遍历范围: i = 1
遍历范围: i = 2
遍历范围: i = 3
遍历范围: i = 4
解构元组: a = 1, b = 2
解构元组: a = 3, b = 4