rust流程控制
控制流
根据条件是否为真来决定是否执行某段代码,以及在条件为真时反复执行某段代码,是绝大多数编程语言的基本组成模块。在 Rust 中,最常见的控制执行流程的结构是 if
表达式和循环。
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。在条件后用花括号包裹的代码块是条件为真时要执行的内容。与 if
表达式中条件关联的代码块有时被称为“分支”(arm),类似于第 2 章“猜数游戏”一节中讨论的 match
表达式的分支。
可以选择性地添加 else
表达式(如上例所示),以便在条件为假时执行另一段代码。如果不提供 else
,条件为假时程序会直接跳过 if
块,继续执行后面的代码。
运行这段代码,你会看到以下输出:
$ cargo runCompiling branches v0.1.0 (file:///projects/branches)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/branches`
condition was true
把 number
的值改成使条件为假的值,看看会发生什么:
let number = 7;
再次运行程序,输出如下:
$ cargo runCompiling branches v0.1.0 (file:///projects/branches)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/branches`
condition was false
注意:这里的条件必须是 bool
类型。如果条件不是 bool
,会得到一个错误。例如,运行以下代码:
文件名:src/main.rs(这段代码无法编译!)
fn main() {let number = 3;if number {println!("number was three");}
}
此时 if
条件的值是整数 3,Rust 会报错:
$ cargo runCompiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types--> src/main.rs:4:8|
4 | if number {| ^^^^^^ expected `bool`, found integer
错误提示 Rust 期望 bool
却得到了整数。与 Ruby 或 JavaScript 等语言不同,Rust 不会自动把非布尔类型转换为布尔类型。必须显式地提供布尔值作为 if
的条件。如果想让代码块只在数字不为 0 时运行,可以这样写:
文件名: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
处理多重条件
可以把 if
和 else
组合成 else if
表达式来处理多重条件。例如:
文件名: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 runCompiling branches v0.1.0 (file:///projects/branches)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/branches`
number is divisible by 3
程序会依次检查每个 if
表达式,执行第一个条件为真的分支。注意 6 也能被 2 整除,但不会打印 number is divisible by 2
,也不会执行 else
块。Rust 一旦找到第一个为真的条件就停止检查其余条件。
过多的 else if
会让代码变得杂乱,如果超过一个,可以考虑用第 6 章介绍的 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 runCompiling branches v0.1.0 (file:///projects/branches)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30sRunning `target/debug/branches`
The value of number is: 5
记住,代码块的值是其最后一个表达式的值,数字本身也是表达式。此时整个 if
表达式的值取决于哪个分支被执行。这意味着每个分支可能返回的值必须是同一类型;在示例 3-2 中,if
分支和 else
分支的结果都是 i32
整数。如果类型不匹配,如下例所示,会得到编译错误:
文件名:src/main.rs(这段代码无法编译!)
fn main() {let condition = true;let number = if condition { 5 } else { "six" };println!("The value of number is: {number}");
}
编译这段代码会报错,if
和 else
分支的类型不兼容:
$ cargo runCompiling 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`
if
分支返回整数,else
分支返回字符串,这行不通,因为变量必须有单一类型,而 Rust 在编译时就要确定变量 number
的类型。如果类型只能在运行时确定,编译器将变得更复杂,对代码的保证也会减少。
用循环实现重复
多次执行一段代码往往很有用。Rust 提供了几种循环,它们会从头到尾执行循环体,然后立刻回到开头。为了实验循环,我们新建一个名为 loops
的项目。
Rust 有三种循环:loop
、while
和 for
。我们逐一尝试。
用 loop
重复执行
关键字 loop
告诉 Rust 反复执行一段代码,直到你显式让它停止。
把 loops
目录下的 src/main.rs
改成以下内容:
文件名:src/main.rs
fn main() {loop {println!("again!");}
}
运行后你会看到 again!
不断打印,直到手动停止。大多数终端支持用 ctrl-c
中断陷入无限循环的程序:
$ cargo runCompiling loops v0.1.0 (file:///projects/loops)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08sRunning `target/debug/loops`
again!
again!
again!
again!
^Cagain!
符号 ^C
表示你按下了 ctrl-c
,其后是否出现 again!
取决于中断信号到达时循环的位置。
Rust 也支持用代码跳出循环。在循环体内使用 break
关键字即可停止循环。还记得第 2 章猜数游戏中,当用户猜对数字时我们用 break
退出程序。
我们也在猜数游戏中用过 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,当等于 10 时用 break counter * 2
返回值。循环后用分号结束赋值语句,最后打印 result
的值 20。
你也可以在循环内使用 return
。break
仅退出当前循环,return
会立即退出整个函数。
用循环标签消除歧义
如果有嵌套循环,break
和 continue
默认作用于最内层循环。可以给循环加上标签,然后用 break
或 continue
指定作用于哪个标签的循环。标签以单引号开头。以下示例包含两个嵌套循环:
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 runCompiling loops v0.1.0 (file:///projects/loops)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58sRunning `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
条件循环 while
程序通常需要在循环中评估条件。条件为真时循环运行,条件为假时调用 break
停止循环。虽然可以用 loop
、if
、else
和 break
组合实现,但这种模式非常常见,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
循环在条件为真时运行代码
这种写法避免了使用 loop
、if
、else
和 break
时必须的嵌套,更清晰。条件为真时运行代码,否则退出循环。
用 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 开始,循环到数组最后一个索引。运行后打印:
$ cargo runCompiling loops v0.1.0 (file:///projects/loops)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32sRunning `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
然而这种方法容易出错:如果索引或测试条件写错,可能导致 panic。例如,把数组改成 4 个元素却忘了把条件改成 index < 4
,就会 panic。此外,每次迭代都要运行时检查索引是否越界,效率较低。
更简洁安全的做法是使用 for
循环,为集合中的每一项执行代码。示例 3-5 展示了 for
循环的写法。
文件名: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 相同。更重要的是,代码更安全,消除了越界或遗漏元素的可能性。如果数组元素数量改变,无需像示例 3-4 那样修改其他代码。
由于安全与简洁,for
循环是 Rust 中最常用的循环结构。即使像示例 3-3 那样需要固定次数的倒数,大多数 Rustaceans 也会用 for
循环。可以使用标准库提供的 Range
生成连续整数序列,并用 rev
反转范围:
文件名:src/main.rs
fn main() {for number in (1..4).rev() {println!("{number}!");}println!("LIFTOFF!!!");
}
这段代码看起来更简洁吧?
总结
本章内容不少:你学习了变量、标量和复合数据类型、函数、注释、if
表达式和循环!为了巩固本章概念,可以尝试编写以下程序:
- 实现华氏度与摄氏度的相互转换。
- 生成第 n 个斐波那契数。
- 利用歌曲中的重复结构打印圣诞颂歌《圣诞十二日》的歌词。
准备好后,我们将讨论 Rust 中其他语言不常见的概念:所有权。