表达式
表达式总是返回同一个类型的值,即使它有多个分支。
语句和表达式
Rust 是一种表达式语言。每一种表达式通常都可以内嵌到另一种表达式中,表达式的求值规则包括指定表达式产生的值和指定其各个子表达式的求值顺序。让我们看看语句与表达式有的区别:语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值。
Rust 有两种语句:声明语句(declaration statements)和表达式语句(expression statements)。
声明语句是在它自己封闭的语句块的内部引入一个或多个名称的语句。示例:
rust
fn main() {
let x = 6;
fn foo() {};
}
表达式语句是对表达式求值并忽略其结果的语句。通常,表达式语句存在的目的是触发对其内部的表达式的求值时的效果。示例:
rust
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
v.pop(); // Ignore the element returned from pop
if v.is_empty() {
v.push(5);
} else {
v.remove(0);
} // Semicolon can be omitted.
[1]; // Separate expression statement, not an indexing expression.
}
块表达式
块表达式或块是一个控制流表达式(control flow expression),同时也是程序项声明和变量声明的匿名空间作用域。 作为控制流表达式,块按顺序执行其非程序项声明的语句组件,最后执行可选的最终表达式(final expression)。 作为一个匿名空间作用域,在本块内声明的程序项只在块本身围成的作用域内有效,而块内由 let
语句声明的变量的作用域为下一条语句到块尾。
块的句法规则为:先是一个 {
,后跟内部属性,再后是任意条语句,再后是一个被称为最终操作数(final operand)的可选表达式,最后是一个 }
。示例:
rust
{
let a = 1;
let b = 2;
let c = a + b;
}
语句之间通常需要后跟分号,但有两个例外: 1、程序项声明语句不需要后跟分号。 2、如果最后一个表达式没有分号,那么将返回表达式的值。
此外,允许在语句之间使用额外的分号,但是这些分号并不影响语义。
在对块表达式进行求值时,除了程序项声明语句外,每个语句都是按顺序执行的。 如果给出了块尾的可选的最终操作数(final operand),则最后会执行它。
块的类型是最此块的最终操作数(final operand)的类型,但如果省略了最终操作数,则块的类型为 ()
。
代码示例:
rust
#![allow(unused)]
fn main() {
fn fn_call() {}
let _: () = {
fn_call();
};
let five: i32 = {
fn_call();
5
};
assert_eq!(5, five);
}
流程控制表达式
if-else
控制语句作用为控制代码在不同的情况下该如何执行。最简单的控制是if
和 else
。另外值得注意的是代码中的条件 必须 是 bool
值。如果条件不是 bool
值,我们将得到一个错误。
rust
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
可以使用 &&
(表示和)和 ||
(表示或)添加更多限制:
rust
fn main() {
let my_number = 5;
if my_number % 2 == 1 && my_number > 0 { // % 2 means the number that remains after diving by two
println!("It's a positive odd number");
} else if my_number == 6 {
println!("It's six")
} else {
println!("It's a different number")
}
}
match
当拥有过多的if
、else
和else if
会导致代码阅读困难,Rust 通过 match
关键字来提供模式匹配。第一个匹配分支会被比对,并且所有可能的值都必须被覆盖。示例:
rust
fn main() {
let number = 13;
println!("Tell me about {}", number);
match number {
// 匹配单个值
1 => println!("One!"),
// 匹配多个值
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// 匹配一个闭区间范围
13..=19 => println!("A teen"),
// 处理其他情况
_ => println!("Ain't special"),
}
let boolean = true;
// match 也是一个表达式
let binary = match boolean {
// match 分支必须覆盖所有可能的值
false => 0,
true => 1,
// 试一试 ^ 将其中一条分支注释掉
};
println!("{} -> {}", boolean, binary);
}
match
也可以进行更复杂一点的匹配:
rust
fn main() {
let sky = "cloudy";
let temperature = "warm";
match (sky, temperature) {
("cloudy", "cold") => println!("It's dark and unpleasant today"),
("clear", "warm") => println!("It's a nice day"),
("cloudy", "warm") => println!("It's dark but not bad"),
_ => println!("Not sure what the weather is."),
}
}
或者可以用匹配来声明一个值:
rust
fn main() {
let my_number = 5;
let second_number = match my_number {
0 => 0,
5 => 10,
_ => 2,
};
}
if-let
if let
表达式在语义上类似于 if
表达式,但是代替条件操作数的是一个关键字 let
,再后面是一个模式、一个 =
和一个检验对象(scrutinee)操作数。 如果检验对象操作数的值与模式匹配,则执行相应的块。 否则,如果存在 else
块,则继续处理后面的 else
块。和 if
表达式一样,if let
表达式也可以有返回值,这个返回值是由被求值的块确定。
rust
#![allow(unused)]
fn main() {
let dish = ("Ham", "Eggs");
// 此主体代码将被跳过,因为模式匹配校验不通过
if let ("Bacon", b) = dish {
println!("Bacon is served with {}", b);
} else {
// 这个块将被执行。
println!("No bacon will be served");
}
// 此主体代码将被执行,因为模式匹配校验通过
if let ("Ham", b) = dish {
println!("Ham is served with {}", b);
}
if let _ = 5 {
println!("Irrefutable patterns are always true");
}
}
在上面的示例中,首先对("Bacon", b)
与dish
进行匹配,如果匹配校验通过,那么执行响应的代码,否则执行else
代码。
loop 循环表达式
使用 loop
重复执行代码直到满足停止条件。示例:
rust
fn main() {
loop {
println!("again!");
}
}
loop
的一个用例是重试可能会失败的操作,比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。如果将返回值加入你用来停止循环的 break
表达式,它会被停止的循环返回。
示例:
rust
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
如果存在嵌套循环,break
和 continue
应用于此时最内层的循环。你可以选择在一个循环上指定一个 循环标签(loop label),然后将标签与 break
或 continue
一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例
rust
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}");
}
while 循环表达式
在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 break
停止循环。这个循环类型可以通过组合 loop
、if
、else
和 break
来实现。然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 while
循环。示例:
rust
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
当条件为真就执行,否则退出循环。
和 if let
类似,while let
可以将代码写的更易懂,示例:
rust
fn main() {
/******* 未使用while let *******/
// 将 `optional` 设为 `Option<i32>` 类型
let mut optional = Some(0);
// 重复运行这个测试。
loop {
match optional {
// 如果 `optional` 解构成功,就执行下面语句块。
Some(i) => {
if i > 9 {
println!("Greater than 9, quit!");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
// ^ 需要三层缩进!
}
// 当解构失败时退出循环:
_ => {
break;
} // ^ 为什么必须写这样的语句呢?肯定有更优雅的处理方式!
}
}
/******* 使用while let *******/
// 将 `optional` 设为 `Option<i32>` 类型
let mut optional = Some(0);
// 这读作:当 `let` 将 `optional` 解构成 `Some(i)` 时,就
// 执行语句块(`{}`)。否则就 `break`。
while let Some(i) = optional {
if i > 9 {
println!("Greater than 9, quit!");
optional = None;
} else {
println!("`i` is `{:?}`. Try again.", i);
optional = Some(i + 1);
}
// ^ 使用的缩进更少,并且不用显式地处理失败情况。
}
// ^ `if let` 有可选的 `else`/`else if` 分句,
// 而 `while let` 没有。
}
for 循环表达式
for in
结构可以遍历一个 Iterator
(迭代器)。创建迭代器的一个最简单的方法是使用区间标记 a..b
。这会生成从 a
(包含此值) 到 b
(不含此值)的,步长为 1 的一系列值。示例:
rust
fn main() {
// `n` 将在每次迭代中分别取 1, 2, ..., 10
// 如果写成 1..=10,即包括10
for n in 1..10 {
println!("{}", n);
}
}
可以使用 for
循环来对一个集合的每个元素执行一些代码,示例:
rust
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
更多表达式及表达式优先级
参考链接:https://doc.rust-lang.org/reference/expressions.html
函数
函数(function)使用 fn
关键字来声明。函数的参数需要标注类型,就和变量一样,如果函数返回一个值,返回类型必须在箭头 ->
之后指定。
函数最后的表达式将作为返回值。也可以在函数内使用 return
语句来提前返一个值,甚至可以在循环或 if
内部使用。示例:
rust
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
可以使用函数名后跟圆括号来调用我们定义过的任意函数。因为程序中已定义 another_function
函数,所以可以在 main
函数中调用它。注意,源码中 another_function
定义在 main
函数之后;也可以定义在之前。Rust 不关心函数定义所在的位置,只要函数被调用时出现在调用之处可见的作用域内就行。
参数
我们可以定义为拥有参数的函数,参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。
在函数签名中,必须 声明每个参数的类型,当定义多个参数时,使用逗号分隔。
示例:
rust
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
another_function
的声明中有一个命名为 x
的参数。x
的类型被指定为 i32
。当我们将 5
传给 another_function
时,println!
宏会把 5
放在格式字符串中包含 x
的那对花括号的位置。
函数的返回值
函数可以向调用它的代码返回值。我们并不对返回值命名,但要在箭头(->
)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。使用 return
关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。示例:
rust
fn five() -> i32 {
5
// 或者使用return关键字进行返回
// return 5
// 或者
// return 5;
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
Rust 有一个叫做 ! 的特殊类型。在类型理论术语中,它被称为 empty type,因为它没有值。我们更倾向于称之为 never type。这个名字描述了它的作用:在函数从不返回的时候充当返回值。例如:
rust
fn bar() -> ! {
// --snip--
panic!();
}
这读 “函数 bar 从不返回”,而从不返回的函数被称为发散函数(diverging functions)。不能创建 ! 类型的值,所以 bar 也不可能返回值。
不过一个不能创建值的类型有什么用呢?比如在下面的代码中:
rust
# use rand::Rng;
# use std::cmp::Ordering;
# use std::io;
#
# fn main() {
# println!("Guess the number!");
#
# let secret_number = rand::thread_rng().gen_range(1..=100);
#
# println!("The secret number is: {secret_number}");
#
# loop {
# println!("Please input your guess.");
#
# let mut guess = String::new();
#
# // --snip--
#
# io::stdin()
# .read_line(&mut guess)
# .expect("Failed to read line");
#
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
#
# println!("You guessed: {guess}");
#
# // --snip--
#
# match guess.cmp(&secret_number) {
# Ordering::Less => println!("Too small!"),
# Ordering::Greater => println!("Too big!"),
# Ordering::Equal => {
# println!("You win!");
# break;
# }
# }
# }
# }
我们学习了 match 的分支必须返回相同的类型。比如,如下代码不能工作:
rust
let guess = match guess.trim().parse() {
Ok(_) => 5,
Err(_) => "hello",
};
这里的 guess 必须既是整型也是字符串,而 Rust 要求 guess 只能是一个类型。那么 continue 返回了什么呢?为什么会允许一个分支返回 u32 而另一个分支却以 continue 结束呢?
正如你可能猜到的,continue 的值是 !。也就是说,当 Rust 要计算 guess 的类型时,它查看这两个分支。前者是 u32 值,而后者是 ! 值。因为 ! 并没有一个值,Rust 决定 guess 的类型是 u32。
描述 ! 的行为的正式方式是 never type 可以强转为任何其他类型。允许 match 的分支以 continue 结束是因为 continue 并不真正返回一个值;相反它把控制权交回上层循环,所以在 Err 的情况,事实上并未对 guess 赋值。
never type 的另一个用途是 panic!。比如错误处理常用的 Option<T>
上的 unwrap 函数,它产生一个值或 panic。这里是它的定义:
rust
impl<T> Option<T> {
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
这里与上面的示例发生了相同的情况:Rust 知道 val 是 T 类型,panic! 是 ! 类型,所以整个 match 表达式的结果是 T 类型。这能工作是因为 panic! 并不产生一个值;它会终止程序。对于 None 的情况,unwrap 并不返回一个值,所以这些代码是有效的。
最后一个有 ! 类型的表达式是 loop:
rust
fn main() {
print!("forever ");
loop {
print!("and ever ");
}
}
这里,循环永远也不结束,所以此表达式的值是 !。但是如果引入 break 这就不为真了,因为循环在执行到 break 后就会终止。
闭包
Rust 中的闭包(closure),是一类能够捕获周围作用域中变量的函数。闭包很容易辨识,因为它使用||
而不是()
。
你可以将一个闭包绑定到一个变量上,然后当你使用它时,它看起来就像一个函数一样。示例:
rust
fn main() {
let my_closure = || println!("This is a closure");
my_closure();
}
在||
之间可以添加输入变量和类型,就像在()
里面添加函数一样。
rust
fn main() {
let my_closure = |x: i32| println!("{}", x);
my_closure(5);
my_closure(5+5);
}
闭包可以接受闭包之外的变量,示例:
rust
fn main() {
let number_one = 6;
let number_two = 10;
// 捕获外部变量和参数进行计算
let my_closure = |x: i32| println!("{}", number_one + number_two + x);
my_closure(5);
}
如果你想强制闭包取得捕获变量的所有权,可以在参数列表前添加 move 关键字。
rust
// 错误示例
#![allow(unused)]
fn main() {
let data = vec![1, 2, 3];
let closure = move || println!("captured {data:?} by value");
// data is no longer available, it is owned by the closure
println!("data: {:#?}", data);
}
move 关键字会将变量的所有权转移,在上面的例子中,data 在闭包后失去所有权,不能再使用。