Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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