if
表达式允许根据条件执行不同的代码分支, 以下代码是一个典型的使用if表达式的例子:
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
所有的 if
表达式都以 if
关键字开头,其后跟一个条件。在这个例子中,条件检查变量 number
的值是否小于 5。在条件为 true
时希望执行的代码块位于紧跟条件之后的大括号中。
也可以包含一个可选的 else
表达式来提供一个在条件为 false
时应当执行的代码块,如果不提供 else
表达式并且条件为 false
时,程序会直接忽略 if
代码块并继续执行下面的代码。
尝试运行该代码, 会得到以下结果:
尝试改变 number
的值使条件为 false
时看看会发生什么:
let number = 7;
再次运行程序并查看输出:
另外值得注意的是代码中的条件 必须 是 bool
值。如果条件不是 bool
值,我们将得到一个错误。
尝试修改代码:
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
这里 if
条件的值是 3
,Rust 抛出了一个错误:
这个错误表明 Rust 期望一个 bool
却得到了一个整数。不像 Ruby 或 JavaScript 这样的语言,Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 if
的条件。例如,如果想要 if
代码块只在一个数字不等于 0
时执行,可以把 if
表达式修改成下面这样:
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
组合来实现多重条件。看以下代码:
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");
}
}
这个程序有四个可能的执行路径。运行后应该能看到如下输出:
当执行这个程序时,它按顺序检查每个 if
表达式并执行第一个条件为 true
的代码块。注意即使 6 可以被 2 整除,也不会输出 number is divisible by 2
,更不会输出 else
块中的 number is not divisible by 4, 3, or 2
。原因是 Rust 只会执行第一个条件为 true
的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
使用过多的 else if
表达式会使代码显得杂乱无章,所以如果有多于一个 else if
表达式,最好重构代码。
因为 if
是一个表达式,我们可以在 let
语句的右侧使用它, 例如:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
number
变量将会绑定到表示 if
表达式结果的值上, 运行一下查看结果:
记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 if
表达式的值取决于哪个代码块被执行。这意味着 if
的每个分支的可能的返回值都必须是相同类型;
if
分支和 else
分支的结果都是 i32
整型。如果它们的类型不匹配会发生什么?,尝试修改代码:
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
当编译这段代码时,会得到一个错误。if
和 else
分支的值类型是不相容的,同时 Rust 也准确地指出在程序中的何处发现的这个问题,如图:
if
代码块中的表达式返回一个整数,而 else
代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 number
变量的类型,这样它就可以在编译时验证在每处使用的 number
变量的类型是有效的。如果number
的类型仅在运行时确定,则 Rust 无法做到这一点;且编译器必须跟踪每一个变量的多种假设类型,那么它就会变得更加复杂,对代码的保证也会减少。
多次执行同一段代码是很常用的,Rust 为此提供了多种 循环(loops)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。
Rust 有三种循环:loop
、while
和 for
loop
关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。
fn main() {
loop {
println!("again!");
}
}
当运行这个程序时,我们会看到连续的反复打印 again!
,直到我们手动停止程序。大部分终端都支持一个快捷键,ctrl-c,来终止一个陷入无限循环的程序。Rust跟其它高级语言一样,叶提供了break关键字停止循环,使用continue关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。
loop
的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 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
的语句。最后打印出 result
的值,也就是 20
。
如果存在嵌套循环,break
和 continue
应用于此时最内层的循环。你可以选择在一个循环上指定一个 循环标签(loop label),然后将标签与 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;
语句将退出外层循环。运行结果如下:
5.while循环
在程序中计算循环的条件也很常见。当条件为 true
,执行循环。当条件不再为 true
,调用 break
停止循环。这个循环类型可以通过组合 loop
、if
、else
和 break
来实现。然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 while
循环。看以下代码:
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
这里,代码对数组中的元素进行计数。它从索引 0
开始,并接着循环直到遇到数组的最后一个索引(这时,index < 5
不再为真)。运行这段代码会打印出数组中的每一个元素,如图:
数组中的所有五个元素都如期被打印出来。尽管 index
在某一时刻会到达值 5
,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
但这个过程很容易出错;如果索引长度或测试条件不正确会导致程序 panic。例如,如果将 a
数组的定义改为包含 4 个元素而忘记了更新条件 while index < 4
,则代码会 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环进行条件检查,以确定在循环的每次迭代中索引是否在数组的边界内。
作为更简洁的替代方案,可以使用 for
循环来对一个集合的每个元素执行一些代码。改造后的代码如下:
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
当运行这段代码时,将看到与while循环中一样的输出。更为重要的是,我们增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug。
for
循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。下面是一个使用 for
循环来倒计时的例子,它还使用了一个方法,rev
,用来反转 range:
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
执行结果如图:
是不是感觉有点点帅气了?