4 函数、语句与表达式

4.1 函数

函数的定义如下:

1
2
3
4
fn function(a:i32,b:i32) -> f64 {
// body
...
}

就这么简单。

与C或python不同的是,你无需关心函数定义在哪个位置(在前还是在后),rust中的函数都可以调用到。

4.2 语句与表达式

下面介绍语句与表达式。一般语句指的是以分号结尾的一些操作,在函数中,语句并不会返回值,比如:

1
2
let a = 3;
let a = a+1;

而表达式则是指没有分号的,并且会在求值后返回一个值,比如:

1
2
3
let x = 5; // 语句
x // 表达式
x+1 // 表达式

在函数中,函数体包括一些语句+最后一行的零个或一个表达式。语句与表达式在写法上就差一个分号,表达式不能包含分号,因此,在函数结尾需要返回值的时候不能带分号,否则它就会变成一条语句,不会返回值。最后,如果不返回任何值,则会隐式地返回一个 ()

当然,你也可以在函数体中使用return来返回值(return带分号和不带分号都可以):

1
2
3
4
fn foo() -> i32 {
let a = 15i32;
return a; // 不带分号,写成 return a 也可以,结果是相同的
}

4.3 发散函数!

发散函数(diverging functions),返回值类型为特殊的!,表示该函数永不返回,一般用于导致程序崩溃的函数:

1
2
3
4
5
fn forever() -> ! {
loop {
//...
};
}

4.4 函数与所有权

当看到这个标题,相信你已经明白了,函数的参数传递也会进行所有权的转移。当然,实现了Copy特征的基本类型是通过拷贝进行的,所以没有所有权的转移,下面的代码不会报错:

1
2
3
4
5
6
7
8
9
10
fn main() {
let a = 5;
let b = add_one(a);
println!("{}", b);
println!("{}", a);
}

fn add_one(value:i32)->i32{
value + 1
}

如果是复合类型,没有实现Copy特征,情况就不同了:

1
2
3
4
5
6
7
8
9
fn main() {
let a = String::from("hello");
say_word(a);
println!("{}", a);
}

fn say_word(value:String){
println!("{}", value);
}

上面的代码会报错,说明在传递参数的时候,进行了所有权转移,a不再拥有值hello,而是转移到了value,在函数执行完毕后,函数的{}作用域结束,value就被drop掉了,因此第四行再次打印a就会报错。

解决方法也很简单,我们传递参数时使用引用即可:

1
2
3
4
5
6
7
8
9
fn main() {
let a = String::from("hello");
say_word(&a);
println!("{}", a);
}

fn say_word(value:&String){
println!("{}", value);
}

因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。这样,程序就可以正常运行了。

5 流程控制

5.1 条件控制

通过ifelse

1
2
3
4
5
6
7
8
fn main() {
let number:i32=5;
if number == 5 {
println!("number is 5");
}else{
println!("number is not 5");
}
}

5.2 循环控制

rust存在三种循环。

for

首先是for

1
2
3
4
5
fn main() {
for i in 0..=10{
println!("number is {}",i);
}
}

其中,0..=10的含义是生成从0到10的连续序列,即[0,10]这样的闭区间,如果不加=0..10,则是左闭右开的区间[0,10)

对于for来说,有一点需要注意,那就是所有权的问题。

首先,对于实现了Copy特征的数组来说,使用for i in 数组并不会将所有权转移,而是进行了内存拷贝,比如:

1
2
3
4
5
6
7
fn main() {
let arr = [1,2,3,4,5];
for i in arr{
println!("number is {}",i);
}
println!("{}",arr[0]) // arr[0]仍然可用
}

但是对于复杂的类型,则会发生所有权转移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fn main() {
let strings = [String::from("123"), String::from("456"),];
for str in strings { // 这里发生了所有权转移
println!("{}", str);
}
println!("{}",strings[0]); // 这里会报错,所有权已经转移
}
// 你将收到以下错误
/*
|
2 | let strings = [String::from("123"), String::from("456"),];
| ------- move occurs because `strings` has type `[String; 2]`, which does not implement the `Copy` trait
3 | // move
4 | for str in strings {
| ------- `strings` moved due to this implicit call to `.into_iter()`
...
7 | println!("{}",strings[0]);
| ^^^^^^^^^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider iterating over a slice of the `[String; 2]`'s content to avoid moving into the `for` loop
|
4 | for str in &strings {
|
*/

解决方法就是使用引用:

1
2
3
4
5
6
7
8
fn main() {
let strings = [String::from("123"), String::from("456"),];

for str in &strings {
println!("{}", str);
}
println!("{}",strings[0]);
}

引用默认是不可变的,因此你无法修改元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let strings = [String::from("123"), String::from("456"),];

for str in &strings {
*str = String::from("hello");
println!("{}", str);
}
println!("{}",strings[0]);
}
// 你将收到以下错误
/*
|
4 | for str in &strings {
| -------- this iterator yields `&` references
5 | *str = String::from("hello");
| ^^^^ `str` is a `&` reference, so the data it refers to cannot be written
*/

需要使用mut 来解决:

1
2
3
4
5
6
7
8
9
fn main() {
let mut strings = [String::from("123"), String::from("456"),];

for str in &mut strings {
*str = String::from("hello");
println!("{}", str);
}
println!("{}",strings[0]);
}

如果你使用过python,那么一定很熟悉enumerate获取可迭代对象的索引,在rust中是这样写的:

1
2
3
4
5
6
7
fn main() {
let a = [6,3,2,4];

for (index, value) in a.iter().enumerate() {
println!("index:{},number:{}", index, value);
}
}

iter()方法会将a转化为迭代器,再使用enumerate()即可在for循环中获取索引。

while

第二种是while,即条件循环,满足某个条件就进行循环,直到不满足条件为止:

1
2
3
4
5
6
7
8
9
fn main() {
let mut i = 0;

while i <= 5 {
println!("{}", i);
// do something
i = i + 1;
}
}

loop

第三种是loop,即无条件循环:

1
2
3
4
5
fn main() {
loop{
println!("hello world");
}
}

这个代码会无限循环下去。因此,对于循环,我们还需要一些可以约束循环的关键字:continuebreak

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut i = 0;
loop{
if i == 3{
i += 1;
continue;
}else if i == 5{
break;
}
println!("{}", i);
i += 1;
}
}

若操作返回一个值,则可能需要将其传递给代码的其余部分:将该值放在 break 之后,它就会被 loop 表达式返回。

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut counter = 0;

let result = loop {
counter += 1;

if counter == 10 {
break counter * 2; // loop中使用`break 表达式`返回值
}
};
}

5.3 循环标签

一个循环表达式可以选择设置一个标签。这类标签被标记为循环表达式之前的生命周期(标签),如:

1
2
3
'foo: loop { break 'foo; }
'bar: while false {}
'humbug: for _ in 0..0 {}

如果循环存在标签,则嵌套在该循环中的带此标签的 break表达式和 continue表达式可以退出此标签标记的循环层或将控制流返回至此标签标记的循环层的头部。

比如:

1
2
3
4
5
6
7
fn main() {
'outer: loop {
while true {
break 'outer;
}
}
}

同理可以用于continue

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut counter = 0;
'outer: loop {
counter += 1;
while counter < 3 {
continue 'outer;
}
break;
}
assert_eq!(counter, 3);
}

6 模式匹配

6.1 match

枚举一节的最后,我们提到了取Some(T)的方法,这里用到的就是模式匹配。首先通过一个例子来介绍match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Animal{
Cat,
Dog,
Bird,
Mouse
}

fn main() {
let cat = Animal::Cat;
match cat {
Animal::Cat => println!("a cat!"),
Animal::Dog | Animal::Bird => println!("a dog or a bird!"),
_ => println!("default"),
}
}

首先创建了一个枚举类型和一个枚举成员的实例,接下来对这个实例cat进行模式匹配,使用match去匹配它对应的类型。下面是match的一些特性:

  • match内部我们需要将所有的可能都列出来。如果你不列出来,编译器会报错:

  • =>的左边,是我们的匹配条件,也叫做模式,右边是匹配成功后执行的代码,也叫做针对该模式进行处理的代码。

  • 使用|表示逻辑或,也就是说只要有一个匹配上,就算匹配成功。

  • 最后的_代表没有匹配成功的默认匹配,和C/C++/typescript中的switch语句内的default很像,作为兜底选项存在。

  • 还需要注意一点就是=>右边可以也可以有多行代码,需要用{}包裹,但无论是单行代码还是多行代码,最后一行一定是一个表达式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    match something {
    case1 => do_something, // 表达式
    case2 => { // 可以是多行代码
    balabala; // 语句
    balabala; // 语句
    expression // 表达式
    },
    _ => println!("...")
    }
  • match本身也是一个表达式,因此你可以这样写,将match匹配到的值绑定到a上:

    1
    2
    3
    let a = match {
    ...
    }
  • 模式匹配从上到下按顺序执行,如果模式匹配了这个值,那么模式之后的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。

模式匹配还有一个比较好用的功能,就是获取模式中绑定的值:

1
2
3
4
5
6
7
8
9
10
11
12
enum Animal{
Cat(String),
Dog(String)
}

fn main() {
let cat = Animal::Cat(String::from("crookshanks"));
match cat {
Animal::Cat(name) => println!("cat, name: {}", name),
Animal::Dog(name) => println!("dog, name: {}", name),
}
}

这里的name就是绑定到枚举成员上的值。

另外,match同样涉及到所有权转移,还是上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#[derive(Debug)]
enum Animal{
Cat(String),
Dog(String)
}

fn main() {
let cat = Animal::Cat(String::from("crookshanks"));
match cat {
Animal::Cat(name) => println!("cat, name: {}", name), // 这里会转移所有权
Animal::Dog(name) => println!("dog, name: {}", name),
} // 这里name被drop掉
println!("{:?}", cat); // cat不拥有所有权

}
/* 你将收到以下错误
error[E0382]: borrow of partially moved value: `cat`
--> src/main.rs:13:22
|
11 | Animal::Dog(name) => println!("dog, name: {}", name),
| ---- value partially moved here
12 | }
13 | println!("{:?}", cat);
| ^^^ value borrowed here after partial move
|
= note: partial move occurs because value has type `String`, which does not implement the `Copy` trait
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: borrow this field in the pattern to avoid moving `cat.0`
|
11 | Animal::Dog(ref name) => println!("dog, name: {}", name),
| +++
*/

根据所有权转移的规则,由于String不是基本类型,没有实现Copy特征,它的所有权先移动到cat,然后通过模式匹配,所有权转移到name,在离开{}的作用域之后,namedrop掉了,再次打印cat时,就会报错。

当然,rust编译器贴心地给出了提示,通过引用&来避免所有权转移:

1
2
3
4
match &cat {
Animal::Cat(name) => println!("cat, name: {}", name),
Animal::Dog(name) => println!("dog, name: {}", name),
}

当然,也可以通过ref,在通过 let 绑定来进行模式匹配或解构时,ref 关键字可用来创建结构体/元组的字段的引用:

1
2
3
4
match cat {
Animal::Cat(ref name) => println!("cat, name: {}", name), // 这里的name就是&String了
Animal::Dog(ref name) => println!("dog, name: {}", name),
}

6.2 if let

在某些场景下,会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,这时使用match就显得很复杂

1
2
3
4
5
6
7
fn main() {
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}
}

我们只想要对 Some(3) 模式进行匹配, 不想处理任何其他 Some<u8> 值或 None 值。但是为了满足 match 表达式(穷尽性)的要求,写代码时必须在处理完这唯一的成员后加上 _ => (),这样会增加不少无用的代码。

我们完全可以用 if let 的方式来实现,在这种只有两个情况的场景下会简洁很多:

1
2
3
4
5
6
fn main() {
let v = Some(3u8);
if let Some(3) = v {
println!("three");
}
}

if let语法格式如下,当然else是可选的:

1
2
3
4
5
if let 匹配值 = 原变量 {
匹配成功的语句块
} else {
没匹配到的语句块
}

你可以使用else if来增加判断项:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let v = Some(4u8);
let need_number = false;
if let Some(3) = v {
println!("three");
} else if need_number{
println!("a number");
} else {
println!(":)");
}
}

6.3 while let

if let 类似,while let 也可以把别扭的 match 改写得好看一些。考虑下面这段使 i 不断增加的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 将 `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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
// 将 `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` 没有。
}

6.4 matches!

matches!是一个宏,它的作用是将一个表达式跟模式进行匹配,如果匹配成功,结果是 true 否则是 false

1
2
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));