前言
上一篇,介绍了Rust语言的
-
常规函数
-
闭包
-
字符串
本篇内容将涉及
-
条件与决策
-
Match表达
-
循环语句
条件与决策(Conditionals and decision making)
Rust中的基本条件语句,就是经典的if-else形式,似乎与其他语言并无很大不同,直接看一个代码实例。
// if_else.rs
fn main() {
let rust_is_awesome = true;
if rust_is_awesome {
println!("Indeed");
} else {
println!("Well, you should try Rust !");
}
}
其实,在Rust中, if-else 结构是一个表达式(expression),而不是一个陈述语句(statement)。
这里需要说明一下,在通常的编程用意中,表达式要有返回值,而陈述语句没有。这就意味着,在Rust的if-else条件表达中,总会有一个返回值,而这个值可能是一个空的()单元类型(unit type ),也可能是一个实际值;
在该表达式中大括号的最后一行负责提供返回值。需要注意一点的是,if和else各自的返回值应该具有同样的类型。而且,可见if 条件表达式也不必非要添加小括号。
上述代码结果
由于返回值的存在,可以将if-else表达式赋值给一个变量,我们看来下面的代码。
// if_assign.rs
fn main() {
let result = if 1 == 2 {
"Wait, what ?"
} else {
"Rust makes sense"
};
println!("You know what ? {}.", result);
}
上述代码结果
不知道读者看到一些区别没有,如果将if-else赋值的话,语句最后要以分号结尾,这个是初始化中的所要求的的格式。如果我们去掉else{}相关的内容,如下所示
fn main() {
let result = if 1 == 2 {
"Wait, what ?"
};
println!("You know what ? {}.", result);
}
我们会得到编译器的报错
我们分析一下原因,这是因为,在if条件为false时,表达式会返回一个空单元值(),而该表达式最后一行是一个&str类型,这意味此时有两个类型不同的潜在返回值,而这个是Rust所不能允许的,所以提示要补一个else{}语句,以保证两个分支有同样类型的返回值。
还有一点要谈,在if语句中字符串后增加的一个分号,使得编译器认为要舍弃该值,我们再来看一个实例代码。
// if_else_no_value.rs
fn main() {
let result = if 1 == 2 {
"Nothing makes sense";
} else {
"Sanity reigns";
};
println!("Result of computation: {:?}", result);
}
上述代码结果
可见,结果是一个空的单元值()。再看println!中,使用的是{:?}而不是{},这是因为空值无法用常规方式打印。
Match表达
Rust中的match语句真的是一个令人非常欢乐而且具有强大表现力的存在,基于C语言中的switch-case,对于多值决策,用起来格外便利,可以认为是该语言设计中的一个小亮点。
我们来看以下的代码实例
// match_expression.rs
fn req_status() -> u32 {
200
}
fn main() {
let status = req_status();
match status {
200 => println!("Success"),
404 => println!("Not Found"),
other => {
println!("Request failed with code: {}", other);
// get response from cache
}
}
}
上述代码结果
这里大致解释一下这几行代码
-
第3-5行的函数req_status,返回的是一个简化的HTTP请求的状态码200
-
第8行,将函数值赋给变量status
-
第9-16,match表达式
-
第9行,match关键词,以及对应匹配的参数status
-
第10-14行,三个分支,称为match arms,每个arm对应可匹配的值和对应操作
-
结构上看,=>符号左边是匹配值,右边是对应操作,同时支持单行与多行代码
-
简单分支,逗号分隔
-
每个分支的返回相同的类型
-
当需要匹配多个可能的值时,match表达式是很不错的方法,而且写起来非常简洁。与if else表达式一样,当let语句用分号分隔时,匹配表达式的返回值也可以赋给变量,所有match arm 返回相同的类型。
循环语句(Loops)
Rust中有三种循环语句,loops,while和for,对于这三种,都可以使用continue 和 break关键字来进行跳过和退出,先来看一个例子。
Loop循环
// loops.rs
fn main() {
let mut x = 1024;
loop {
if x < 0 {
break;
}
println!("{} more runs to go", x);
x -= 1;
}
}
上述代码结果
...
loop代表一个无限循环,该代码不难理解,在其中不断削减x的值,直到小于0为止。
在Rust中,loop能够对循环体添加命名标签,经常用于多个循环存在,而只想用break退出其中一个的情形,看下代码。
// loop_labels.rs
fn silly_sub(a: i32, b: i32) -> i32 {
let mut result = 0;
'increment: loop {
if result == a {
let mut dec = b;
'decrement: loop {
if dec == 0 {
// breaks directly out of 'increment loop
break 'increment;
} else {
result -= 1;
dec -= 1;
}
}
} else {
result += 1;
}
}
result
}
fn main() {
let a = 10;
let b = 4;
let result = silly_sub(a, b);
println!("{} minus {} is {}", a, b, result);
}
上述代码结果
在代码中,做了一个非常矫情而且低效的减法,只是为了演示在嵌套循环中使用标签。在内部的‘decrement标签中,当dec等于0时,我们可以传递一个标签给break(这里是‘increment’),并从外部的‘increment循环退出。
请读者记住这个场景,在后续的综合应用中还会有所涉及。
While循环
这里我们看下while循环
// while.rs
fn main() {
let mut x = 1000;
while x > 0 {
println!("{} more runs to go", x);
x -= 1;
}
}
For循环
请读者们注意了,for循环是很值得在这里说一下的,单看语法,与之前提到loop区别不大,然而在事实上,这两者在执行过程中非常不同。
Rust中的for循环是与迭代器iterators结构体相关联的,而这一点我们会在后续讲到高级概念时候详细介绍。简言之,Rust中的for循环只适用于可转换为迭代器的类型。Range类型就是一种迭代器,可以指一个数字范围,例如 0 到10,我们可以这样使用,见下面实例:
// for_loops.rs
fn main() {
// does not include 10
print!("Normal ranges: ");
for i in 0..10 {
print!("{},", i);
}
println!(); // just a newline
print!("Inclusive ranges: ");
// counts till 10
for i in 0..=10 {
print!("{},", i);
}
}
上述代码结果如下
上述代码中的第6行的0..10,左闭右开,和第13行中0..=10,左闭右闭都是Range类型相关的语法。
结语
学完这三个部分,大家是不是可以感受一点语言设计中的所体现的匠心了呢。
下一节我们开始讲一下用户自定义的数据类型,涉及结构体(Structs)和枚举类型(Enums)。
主要参考和建议读者进一步阅读的文献
https://doc.rust-lang.org/book
1.Rust编程之道,2019, 张汉东
2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
3.Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger
4.Beginning Rust ,2018,Carlo Milanesi
5.Rust Cookbook,2017,Vigneshwer Dhinakaran