Rust 中的 if 语句和 C++ 中的大致相似。不同之一在于,花括号必须写,但被测表达式的圆括号可以不写。另一点在于,if 是表达式,因此可以像 C++ 中的 ?: 三元运算符一样使用(回顾一下上节内容:如果块中最后一个表达式没有以分号结尾,则表达式成为块的返回值)。Rust 中没有三元运算符 ?:。以下两个函数的作用相同:
fn foo(x: i32) -> &'static str {
let result: &'static str;
if x < 10 {
result = "less than 10";
} else {
result = "10 or more";
}
return result;
}
fn bar(x: i32) -> &'static str {
if x < 10 {
"less than 10"
} else {
"10 or more"
}
}(为何不用 mut result?foo 中的代码使得 result 不可更改,只是可能在两处初始化。Rust 可以发现,return result 时结果必定会被初始化。)
第一个函数差不多就是 C++ 写法的翻版。第二个则是更好的 Rust 编码风格。
也可以写出 let result = if x < 10 ... 这样的代码。
Rust 带有 while 循环,同样和 C++ 相似:
fn main() {
let mut x = 10;
while x > 0 {
println!("Current value: {}", x);
x = -1;
}
}Rust 中没有 do...while 循环,不过有无尽循环的 loop 语句:
fn main() {
loop {
println!("Just Looping");
}
}Rust 带有 break 和 continue,和 C++ 一致。
Rust 同样带有 for 循环,但有些微不同。假设有一个整数类型的可扩张数组(vector),想将其全部打印出来(我们之后会涵盖动态和静态数组,迭代器和泛型的细节。目前我们只需知道,Vec<T> 是元素类型为 T 的序列,iter() 返回任何想要迭代的对象的迭代器)。简单的 for 循环如下:
fn print_all(all: Vec<i32>) {
for a in all.iter() {
println!("{}", a);
}
}原文待补充:编写使用 &all 和 all 而非 all.iter() 的代码
要使用索引值遍历 all(类似 C++ 中为数组编写的 for 循环),可以写成
fn print_all(all: Vec<i32>) {
for i in 0..all.len() {
println!("{}: {}", i, all[i]);
}
}len 函数的功能应该是不言而喻了。原文待补充:区间表达式(range notation)
使用枚举迭代器可以将上例写成更偏向 Rust 风格的版本:
fn print_all(all: Vec<i32>) {
for (i, a) in all.iter().enumerate() {
println!("{}: {}", i, a);
}
}其中,enumerate() 链接自迭代器 iter(),在迭代时产出(yield)当前下标和元素。
下例结合了借用指针中涵盖的更高级的主题。 假设有一个整数的动态数组,想要调用函数,传容器的引用,并就地修改容器。此处 for 循环会用到可修改迭代器,这种迭代器可提供可修改的引用。* 解引用,C++ 程序员应该很熟悉了:
fn double_all(all: &mut Vec<i32>) {
for a in all.iter_mut() {
*a += *a;
}
}Rust 带有匹配表达式,和 C++ 的 switch 语句类似,不过强大很多。下面的简单例子应该挺好懂的:
fn print_some(x: i32) {
match x {
0 => println!("x is zero"),
1 => println!("x is one"),
10 => println!("x is ten"),
y => println!("x is something else {}", y),
}
}上例中有一些语法差异:使用 => 从匹配的值指向要执行的表达式,不同匹配分支之间使用 , 分隔(最后一个 , 可以不写)。还有一些不太明显的语义差异:匹配必须写全,换言之必须覆盖匹配表达式(上例的 x)所有可能的值。尝试把 y => ... 一行删除,看看会发生什么(译者注:编译错误);因为我们只匹配了 0、1 和 10,明显还有很多整数没有匹配。最后一个匹配分支中,y 绑定到要匹配的值(上例中的 x)。也可以写成:
fn print_some(x: i32) {
match x {
x => println!("x is something else {}", x)
}
}匹配分支中的 x 引入了新变量,隐藏了实参 x,和在内层作用域中声明(同名)变量类似。
如果不想给变量起名,可以用 _ 表示无名变量,这与使用通配符匹配类似。如果什么都不想做,可以加一个空分支:
fn print_some(x: i32) {
match x {
0 => println!("x is zero"),
1 => println!("x is one"),
10 => println!("x is ten"),
_ => {}
}
}另一个语义差异在于,不会从一个分支直落(fall through)到另一个分支(译者注:指 C++ 中 switch 语句中不写 break 使得控制流接着运行下一个 case 而非离开 switch 语句的情况),因此,此处的 switch 的运行类似 if...else if...else。
我们在之后的文章中会发现,匹配表达式极其强大。本文中,我再介绍两个机制:分支中为值使用的或运算符,以及分支中的 if。下面的例子应该是不言自明了:
fn print_some_more(x: i32) {
match x {
0 | 1 | 10 => println!("x is one of zero, one, or ten"),
y if y < 20 => println!("x is less than 20, but not zero, one, or ten"),
y if y == 200 => println!("x is 200 (but this is not very stylish)"),
_ => {}
}
}(译者注:上例类似数学中分段函数的写法。比起 C++ 中的 switch,这种方式胜在写法简洁且表意明确。)
和 if 表达式一样,match 语句其实也是表达式。据此我们可以将上例改为:
fn print_some_more(x: i32) {
let msg = match x {
0 | 1 | 10 => "one of zero, one, or ten",
y if y < 20 => "less than 20, but not zero, one, or ten",
y if y == 200 => "200 (but this is not very stylish)",
_ => "something else"
};
println!("x is {}", msg);
}注意右花括号后有个分号,这是因为,let 语句是个语句,必须以 let msg = ...;这种形式书写。我们在省略号处填入匹配表达式(表达式本身无需加分号),但是 let 语句需要分号。我总是在这儿栽跟头。(译者注:正如很多 C++ 初学者忘记在类定义后加分号一样,Rust 初学者可能会忘记在这种长相复杂的语句后加分号。)
动机:Rust 的匹配语句避免了 C++ 的 switch 语句的常见漏洞:不会出现忘记写 break 导致的控制流直落;如果匹配枚举项(后面介绍),编译器会确保 match 语句涵盖了所有枚举项。
最后简单补充一下,和 C++ 一样,Rust 中带有方法(译者注:“方法”与 C++ 中的“成员函数”和 Java 的“方法”是一个意思)。方法永远使用 . 运算符调用(不用 ->,后面一篇文章详细说明)。我们在上文见过了一些例子(len,iter)。之后会补充定义和调用方法的内容。读者依据 C++ 和 Java 形成的多数假设或许都是正确的。