Rust 流程控制
本笔记主要来自于 Rust 程序设计语言 中文版 [3.5],旨在记录个人学习过程中的重点和心得体会。在记录过程中,笔者会对文档中的某些内容进行摘抄或修改,并添加自己的注释或说明。如有不当之处,请指正。
流程控制可以根据条件是否为 true
来执行某些特定的代码,或者根据条件是否为 true
来重复执行一段代码是大部分编程语言的基本组成部分。Rust 中最常见的用来控制执行流的是 if
表达式和循环。
if 表达式
if
表达式允许根据条件执行不同的代码分支。你提供一个条件“如果条件满足,那么执行这个分支,否则不执行”。
fn main() {
let number = 3;
if number >= 3 {
println!("number 大于3")
} else {
println!("number 小于3")
}
}
所有的 if
表达式都以 if
关键字开头,后面跟着一个条件表达式。在这个例子中,如果 number
大于3那么希望执行后边紧跟着的代码块 {}
。 if
表达式中条件关联的代码块有时被叫做 arms。
当然也可以包含一个可选的 else
表达式来提供一个当条件不成立时执行的代码块。如果不提供 else
那么程序会忽略 if
代码块继续往下执行。
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [9:49:52]
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
Finished dev [unoptimized + debuginfo] target(s) in 4.64s
Running `target/debug/branches`
number 大于3
如果尝试改变 number
为2,那么会执行 else
代码块中的代码
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [9:53:11]
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.99s
Running `target/debug/branches`
number 小于3
注意:if
中的条件必须是 bool
类型,如果不是 bool
类型,他会抛出一个错误,例如运行以下代码:
if 3 {
println!("hello world")
}
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:07:55]
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
error[E0308]: mismatched types
--> src/main.rs:3:9
|
3 | if 3 {
| ^ expected `bool`, found integer
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
这个错误表明 Rust 期望得到一个 bool
,但实际得到了一个 integer
。它不像 JavaScript 那样,Rust 并不会尝试将非布尔值转为布尔值。必须总是显示的使用一个 bool
类型作为 if
的条件。
使用 else if 处理多重条件
如果我们需要判断好多不同场景的条件,那么我们可以使用 else if
表达式与 if
和else
组合来实现多重条件。
use std::io;
fn main() {
let mut number = String::new();
io::stdin().read_line(&mut number).expect("fail to read");
let number: i32 = number.trim().parse().expect("fail to parse");
if number == 0 {
println!("输入0")
} else if number == 1 {
println!("输入1:1")
} else if number == 1 {
println!("输入2:1")
} else if number > 10 {
println!("输入大于10的数")
} else {
println!("输入大于1小于10的数")
}
}
我们多次执行并输入这些数字来查看输出结果
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:22:24] C:101
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/branches`
1
输入1:1
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:22:41]
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/branches`
3
输入大于1小于10的数
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:22:45]
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/branches`
14
输入大于10的数
当程序执行时,它按顺序检查每一个 if
表达式并执行第一个条件为 true
的代码块,例如第一次执行的时候输入了1,那么第3个分支不会被执行,因为 Rust 只会执行第一个条件为 true
的代码块,并且一旦找到一个以后,它都不会再检查剩下的条件了。
如果使用过多的
else-if
表达式会使代码显得杂乱无章,所以如果有多个else-if
表达式,为了阅读最好重构代码,可以考虑match
表达式。
在 let 语句中使用 if 表达式
因为 if
是一个表达式,所以我们可以在 let
表达式的右侧使用它
fn main(){
let mut number = String::new();
io::stdin().read_line(&mut number).expect("fail to read");
let number: i32 = number.trim().parse().expect("fail to parse");
let result = if number >= 5 {
"大于等于5"
} else {
"小于5"
};
println!("result {}", result)
}
运行这个程序并输入值:5
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:43:51]
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
5
result 大于等于5
代码块的值是最后一个表达式的值,而数字本身就是一个表达式,结合我之前学到的语句和表达式就能够理解这些代码。整个 if
表达式的值取决于哪个代码块被执行,这意味着 if
表达式的每个分支的可能的返回值类型必须是一样的。if
和 else
分支的值类型都是 &str
,如果类型不一样,那么就会抛出一个错误:
# clover @ MacBook-Pro in ~/dev/rust/learn/branches on git:master x [10:44:49]
$ cargo run
Compiling branches v0.1.0 (/Users/clover/dev/rust/learn/branches)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:38:9
|
35 | let result = if number >= 5 {
| __________________-
36 | | "大于等于5"
| | ----------- expected because of this
37 | | } else {
38 | | 5
| | ^ expected `&str`, found integer
39 | | };
| |_____- `if` and `else` have incompatible types
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
在 if
代码块中返回 &str
而 else
代码块返回 i32
类型,这样是不可以的,因为在 Rust 中同一个变量必须只有一个类型,并且 Rust 在编译的时候就需要知道 result
变量确切的类型,这样他就可以验证在每处使用的 result
变量的类型都是有效的。Rust 并不能处理只能在运行时知道类型的变量,这样会使得编译器变得更复杂而且只能为代码提供更少的保障,因为它需要记录变量的多种可能的类型。
使用循环重复执行
Rust 提供了多种循环来实现多次执行同一代码。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。
Rust 有三种循环:loop
、for
、 while
使用 loop 重复执行代码
loop
关键字可以告诉 Rust 你想要一遍又一遍执行代码块中的代码直到要求停止。
fn main() {
loop {
println!("Hello, world!");
}
}
当这段代码运行时我们可以看到,它会在终端中一直输出 hello world直至我们手动停止程序,大部分终端都是 control + c 来强制停止一个真正运行的程序。
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [11:51:23] C:130
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.79s
Running `target/debug/loops`
Hello, world!
...
Hello, wor^C
Rust 提供了一个 break
表达式来退出循环,我们可以在某些条件符合要求后就退出,否则下面的代码永远无法执行
fn main() {
let mut i = 0;
loop {
if i > 3 { break; }
println!("Hello, world!");
i += 1;
}
}
从循环中返回
loop
的一个用例时重试一段可能会失败的操作,比如检查线程是否完成了任务。你可能需要将操作的结果返回给其它代码,这时候我们可以将结果加入到 break
表达式中,用来停退出循环和返回结果。
fn main() {
let mut i = 0;
let result = loop {
if i > 3 { break i; }
i += 1;
};
println!("result ===>>> {}", result)
}
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [12:00:37] C:101
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/loops`
b ===>>> 4
在循环开始之前,先声明一个 i
变量用于循环计数。在 loop
开始循环后,累计添加 i
,直到 i
大于3,大于3后通过 break
表达式退出循环并将 i
作为结果返回并绑定到 result
变量。
while 条件循环
在程序中计算循环的条件也非常常见。当条件为 true
时执行循环,当条件为 false
时 break
退出循环。这种循环我们可以通过 loop
结合 if else
、break
来实现,例如上面一节介绍 loop
的代码。
因为这种模式在开发中用的频率太高了,Rust 给出了一个 while
可以不用手动 break
的循环,他被称为 while
循环。上面章节的示例,我用 while
重构会非常轻松
fn main() {
let mut i = 0;
while i < 3 {
i += 1
};
println!("result {}", i)
}
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [14:21:01] C:101
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.75s
Running `target/debug/loops`
result 3
这种循环消除了使用 loop
结合 if else
、break
时所必需的嵌套,代码更加清晰。当条件为 false
时跳出循环。
使用 for 遍历集合
对于集合,我们当然可以通过 whild
或者 loop
来循环
fn main() {
let mut i = 0;
let arr = [1, 2, 3, 4, 5];
while i < 5 {
println!("arr {}", arr[i]);
i += 1;
}
}
这段代码是通过对集合进行计数。它的索引从 0 开始,并一直循环直到最后一个索引,在每次循环中都打印出当前索引对应的元素。
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [14:27:21] C:130
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 1.04s
Running `target/debug/loops`
arr 1
arr 2
arr 3
arr 4
arr 5
这段代码可能会出错,因为如果索引长度不正确会导致程序 panic
。这也会让程序运行的更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
作为更简洁的替代方案,可以使用 for
循环来便利一个集合。
fn main() {
let arr = [1, 2, 3, 4, 5];
for it in arr.iter() {
println!("element {}", it);
}
}
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [14:35:28]
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/loops`
element 1
element 2
element 3
element 4
element 5
这个段代码运行结果和使用 while
结果一样,但是最重要的是它增强了代码的安全性,并且消除了可能超出数组索引或遍历长度不够导致丢失元素而导致的BUG。
除了索引 for
还可以对一个区间进行遍历
for num in (1..5).rev() {
println!("num {}", num)
}
# clover @ MacBook-Pro in ~/dev/rust/learn/loops on git:master x [14:40:58] C:101
$ cargo run
Compiling loops v0.1.0 (/Users/clover/dev/rust/learn/loops)
Finished dev [unoptimized + debuginfo] target(s) in 1.39s
Running `target/debug/loops`
num 4
num 3
num 2
num 1
上面的区间调用 rev
方法,主要作用是将区间进行反转,例如原来的1-4反转后变成了4-1