控制流


根据条件是否为 true 来运行一些代码并在条件为 true 时重复运行某些代码的能力是大多数编程语言的基本构建块。允许您控制 Rust 代码执行流程的最常见结构是 if 表达式和循环。


如果表达 式


if 表达式允许您根据条件对代码进行分支。您提供一个条件,然后声明:“如果满足此条件,则运行此代码块。如果不满足条件,请不要运行此代码块。


projects 目录中创建一个名为 branches 的新项目,以浏览 if 表达式。在 src/main.rs 文件中,输入以下内容:


文件名: src/main.rs

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}


所有 if 表达式都以关键字 if 开头,后跟条件。在这种情况下,条件检查变量 number 是否具有 值小于 5。如果条件为 true。与 if 表达式中的条件关联的代码块有时称为 arms,就像我们在 “比较” 中讨论的 match 表达式中的 arms 一样 猜秘密数字“部分。


或者,我们还可以包含一个 else 表达式,我们在这里选择这样做,以便在条件评估为 false 时为程序提供要执行的替代代码块。如果您不提供 else 表达式并且条件为 false,则程序将跳过 if 块并继续执行下一段代码。


尝试运行此代码;您应该会看到以下输出:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was true


让我们尝试将 number 的值更改为使条件 false 以查看会发生什么:

fn main() {
    let number = 7;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}


再次运行该程序,并查看输出:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
condition was false


还值得注意的是,此代码中的 condition 必须是 bool。如果条件不是 bool,我们将收到错误。例如,尝试运行以下代码:


文件名: src/main.rs

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}


if 条件这次的计算结果为 3,并且 Rust 会抛出一个错误:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
 --> src/main.rs:4:8
  |
4 |     if number {
  |        ^^^^^^ expected `bool`, found integer

For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error


该错误表明 Rust 期望得到一个 bool,但得到了一个整数。与 Ruby 和 JavaScript 等语言,Rust 不会自动尝试 将非布尔类型转换为布尔类型。您必须明确并始终提供 if 中,以布尔值作为其条件。例如,如果我们希望 if 代码块仅在数字不等于 0 时运行,我们可以将 if expression 设置为以下内容:


文件名: src/main.rs

fn main() {
    let number = 3;

    if number != 0 {
        println!("number was something other than zero");
    }
}


运行此代码将打印 number was something other than zero .


使用 else if 处理多个条件


您可以通过在 else if 中组合 ifelse 来使用多个条件 表达。例如:


文件名: src/main.rs

fn main() {
    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");
    }
}


该程序有四种可能的路径。运行后,您应该会看到以下输出:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/branches`
number is divisible by 3


当此程序执行时,它会依次检查每个 if 表达式,并执行条件计算结果为 true 的第一个主体。请注意,即使 6 能被 2 整除,我们也没有看到输出数字能被 2 整除,也没有看到 else 中的 number is not divisible by 4, 3, or 2 文本 块。这是因为 Rust 只执行第一个 true 的块 condition,一旦找到一个,它甚至不会检查其余的。


使用过多的 else if 表达式会使代码混乱,因此,如果有多个表达式,则可能需要重构代码。第 6 章描述了一种强大的 Rust 分支结构,称为 match,用于这些情况。


let 语句中使用 if


因为 if 是一个表达式,所以我们可以在 let 的右侧使用它 语句将结果分配给一个变量,如示例 3-2 所示。


文件名: src/main.rs
fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

示例 3-2:将 if 表达式的结果分配给变量


number 变量将根据 if 的结果绑定到一个值 表达。运行以下代码以查看会发生什么:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/branches`
The value of number is: 5


请记住,代码块的计算结果为其中的最后一个表达式,而数字本身也是表达式。在这种情况下,整个 if 表达式的值取决于执行的代码块。这意味着有可能成为 if 的每个分支的结果的值必须为同一类型;在示例 3-2 中,if arm 和 else 的结果 arm 是 i32 整数。如果类型不匹配,如以下示例所示,我们将收到错误:


文件名: src/main.rs

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {number}");
}


当我们尝试编译此代码时,我们将收到一个错误。ifelse 臂具有不兼容的值类型,Rust 准确地指示在程序中查找问题的位置:

$ cargo run
   Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:4:44
  |
4 |     let number = if condition { 5 } else { "six" };
  |                                 -          ^^^^^ expected integer, found `&str`
  |                                 |
  |                                 expected because of this

For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error


if 块中的表达式计算结果为整数,else 块中的表达式计算结果为字符串。这不起作用,因为变量必须 有一个类型,并且 Rust 需要在编译时知道 number 变量是肯定的。知道 number 的类型可以让编译器验证该类型在我们使用 number 的任何地方都有效。如果数字的类型仅在运行时确定,Rust 将无法做到这一点;如果编译器必须跟踪任何变量的多个假设类型,则编译器将更复杂,并且对代码的保证会更少。


使用 Loops 重复


多次执行一个代码块通常很有用。对于此任务,Rust 提供了多个循环,这些循环将遍历循环体内部的代码直到结束,然后立即从头开始。为了试验 loops,让我们创建一个名为 loops 的新项目。


Rust 有三种类型的循环:loopwhilefor。让我们逐一尝试。


使用循环重复代码


loop 关键字告诉 Rust 永远一遍又一遍地执行一段代码,或者直到你明确告诉它停止。


例如,将 loops 目录中的 src/main.rs 文件更改为如下所示:


文件名: src/main.rs

fn main() {
    loop {
        println!("again!");
    }
}


当我们运行这个程序时,我们会再次看到!一遍又一遍地连续打印 直到我们手动停止程序。大多数终端都支持键盘快捷键 ctrl - c 中断卡在连续循环中的程序。试一试:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!


符号 ^C 表示您按下 ctrl - c 的位置。您可能会也可能不会再次看到单词 ,具体取决于代码在接收到中断信号时在循环中的位置。


幸运的是,Rust 还提供了一种使用代码跳出循环的方法。您可以在循环中放置 break 关键字,以告诉程序何时停止 执行循环。回想一下,我们在 第 2 章的“猜对后退出”部分,当用户通过猜对数字赢得游戏时退出程序。


我们还在猜谜游戏中使用了 continue,它在循环中告诉程序跳过此循环迭代中的任何剩余代码并转到下一次迭代。


从循环返回值


循环的用途之一是重试您知道可能会失败的作,例如检查线程是否已完成其作业。您可能还需要将该作的结果从循环中传递给代码的其余部分。为此,您可以在用于停止循环的 break 表达式之后添加要返回的值;该值将从循环中返回,以便您可以使用它,如下所示:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}


在循环之前,我们声明一个名为 counter 的变量,并将其初始化为 0. 然后我们声明一个名为 result 的变量来保存循环返回的值。在循环的每次迭代中,我们将 counter 变量加 1,然后检查 counter 是否等于 10。当它出现时,我们使用 break 关键字,其值为 counter * 2。在循环之后,我们使用分号结束将值分配给 result 的语句。最后,我们打印 result 中的值,在本例中为 20


您也可以从循环内部返回break 仅退出当前循环,而 return 始终退出当前函数。


Loop 标签以消除多个 Loop 之间的歧义


如果 Loop 中有 Loops,则 breakcontinue 将应用于该点的最内侧 Loop。您可以选择在循环上指定一个循环标签,然后可以与 breakcontinue 一起使用,以指定这些关键字应用于已标记的循环,而不是最内层的循环。循环标签必须以单引号开头。下面是一个包含两个嵌套循环的示例:

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}


外部循环具有标签 'counting_up,它将从 0 到 2 计数。没有标签的内循环从 10 到 9 倒计时。未指定标签的第一个 break 将仅退出内部循环。break 'counting_up; 语句将退出外部循环。此代码打印:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2


带有 while 的条件循环


程序通常需要评估循环中的条件。当条件为 true 时,循环运行。当条件不再为 true 时,程序调用 break 并停止循环。可以使用 loopifelsebreak 的组合来实现这样的行为;如果您愿意,您现在可以在程序中尝试。然而,这种模式是如此普遍,以至于 Rust 有一个内置的语言结构,称为 while 循环。在示例 3-3 中,我们使用 while 循环程序 3 次,每次倒计时,然后在循环后打印一条消息并退出。


文件名: src/main.rs
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

示例 3-3:使用 while 循环在条件为 true 时运行代码


此构造消除了大量嵌套,如果您使用 loopifelsebreak 一起,这样就更清晰了。当条件的计算结果为 true 时,代码将运行;否则,它将退出循环。


使用 for 循环遍历集合


您还可以使用 while 构造来循环访问集合的元素,例如数组。例如,示例 3-4 中的循环打印数组 a 中的每个元素。


文件名: src/main.rs
fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index += 1;
    }
}

示例 3-4:使用 while 循环遍历集合的每个元素


在这里,代码通过数组中的元素进行计数。它从索引开始 0,然后循环,直到到达数组中的最终索引(即,当索引 < 5 不再为 true 时)。运行此代码将打印数组中的每个元素:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
     Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50


正如预期的那样,所有 5 个 array 值都显示在终端中。即使索引 将在某个时候达到值 5,则循环在尝试从数组中获取第六个值之前停止执行。


但是,这种方法容易出错;如果 Index Value 或 Test Condition 不正确,可能会导致程序 panic。例如,如果将 a 数组的定义更改为具有四个元素,但忘记将条件更新为 while index < 4,则代码将 panic。它也很慢,因为编译器会添加运行时代码,以便在循环的每次迭代中执行索引是否在数组边界内的条件检查。


作为更简洁的替代方法,您可以使用 for 循环并为集合中的每个项目执行一些代码。for 循环类似于示例 3-5 中的代码。


文件名: src/main.rs
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

示例 3-5:使用 for 循环遍历集合的每个元素


当我们运行这段代码时,我们将看到与示例 3-4 中相同的输出。更重要的是,我们现在提高了代码的安全性,并消除了因超出数组末尾或不够远而丢失某些项目而可能导致的 bug 的可能性。


使用 for 循环,如果你更改了数组中值的数量,你不需要记住更改任何其他代码,就像你使用示例 3-4 中使用的方法一样。


for 循环的安全性和简洁性使它们成为 Rust 中最常用的循环结构。即使在你想运行一些代码一定次数的情况下,比如示例 3-3 中使用 while 循环的倒计时示例,大多数 Rustacean 也会使用 for 循环。执行此作的方法是使用标准库提供的 Range,它按顺序生成所有数字,从一个数字开始,在另一个数字之前结束。


以下是使用 for 循环和另一种我们尚未讨论的方法 rev 来反转范围的倒计时情况:


文件名: src/main.rs

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}


这段代码好一点,不是吗?


总结


你成功了!这是相当长的一章:您学习了变量、标量和复合数据类型、函数、注释、if 表达式和循环!要练习本章中讨论的概念,请尝试构建程序来执行以下作:


  • 在华氏度和摄氏度之间转换温度。

  • 生成第 n个斐波那契数。

  • 打印圣诞颂歌“The Twelve Days of Christmas”的歌词,利用歌曲中的重复。


当你准备好继续时,我们将讨论 Rust 中一个没有 通常存在于其他编程语言中:所有权。