Update: unified format

pull/357/head
Allan Downey 3 years ago
parent f9fb525546
commit cd31fbfbc5

@ -7,7 +7,7 @@
## 使用if来做分支控制
> if else无处不在 - `鲁迅说`
但凡你能找到一门编程语言没有`if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else`**表达式**根据条件执行不同的代码分支:
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
```rust
if condition == true {
// A...
@ -16,7 +16,7 @@ if condition == true {
}
```
该代码读作:若`condition`条件为`true`,则执行`A`代码,否则执行`B`代码。
该代码读作:若 `condition` 条件为 `true`,则执行 `A` 代码,否则执行 `B` 代码。
先看下面代码:
```rust
@ -33,8 +33,8 @@ fn main() {
```
以上代码有以下几点要注意:
- **`if`语句块是表达式**,这里我们使用`if`表达式的返回值来给`number`进行赋值:`number`的值是`5`。
- 用`if`来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](../appendix/expressions.md#if表达式)),此处返回的`5`和`6`就是同一个类型,如果返回类型不一致就会报错
- **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5`
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](../appendix/expressions.md#if表达式)),此处返回的 `5` `6` 就是同一个类型,如果返回类型不一致就会报错
```console
error[E0308]: if and else have incompatible types
@ -53,7 +53,7 @@ error[E0308]: if and else have incompatible types
```
#### 使用else if来处理多重条件
可以将`else if`与`if`、`else`组合在一起实现更复杂的条件分支判断:
可以将 `else if` `if`、`else` 组合在一起实现更复杂的条件分支判断:
```rust
fn main() {
let n = 6;
@ -69,21 +69,21 @@ fn main() {
}
}
```
程序执行时,会按照自上至下的顺序执行每一个分支判断,一旦成功,则跳出`if`语句块,最终本程序会匹配执行`else if n % 3 == 0`的分支,输出`"number is divisible by 3"`。
程序执行时,会按照自上至下的顺序执行每一个分支判断,一旦成功,则跳出 `if` 语句块,最终本程序会匹配执行 `else if n % 3 == 0` 的分支,输出 `"number is divisible by 3"`
有一点要注意,就算有多个分支能匹配,也只有第一个匹配的分支会被执行!
如果代码中有大量的`else if `会让代码变得极其丑陋,不过不用担心,下一章的`match`专门用以解决多分支模式匹配的问题。
如果代码中有大量的 `else if ` 会让代码变得极其丑陋,不过不用担心,下一章的 `match` 专门用以解决多分支模式匹配的问题。
## 循环控制
循环无处不在,上到数钱,下到数年,你能想象的很多场景都存在循环,因此它也是流程控制中最重要的组成部分之一。
在Rust语言中有三种循环方式`for`、`while`和`loop`,其中`for`循环是Rust循环王冠上的明珠。
Rust 语言中有三种循环方式:`for`、`while` `loop`,其中 `for` 循环是 Rust 循环王冠上的明珠。
#### for循环
`for`循环是Rust的大杀器
`for` 循环是 Rust 的大杀器:
```rust
fn main() {
for i in 1..=5 {
@ -92,23 +92,23 @@ fn main() {
}
```
以上代码循环输出一个从1到5的序列简单粗暴核心就在于`for`和`in`的联动,语义表达如下:
以上代码循环输出一个从 1 5 的序列,简单粗暴,核心就在于 `for` `in` 的联动,语义表达如下:
```rust
for 元素 in 集合 {
// 使用元素干一些你懂我不懂的事情
}
```
这个语法跟`javascript`还蛮像,应该挺好理解。
这个语法跟 JavaScript 还蛮像,应该挺好理解。
注意,使用`for`时我们往往使用集合的引用形式除非你不想在后面的代码中继续使用该集合比如我们这里使用了container的引用。如果不使用引用的话所有权会被转移到`for`语句块中,后面就无法再使用这个集合了)
注意,使用 `for` 时我们往往使用集合的引用形式,除非你不想在后面的代码中继续使用该集合(比如我们这里使用了 `container` 的引用)。如果不使用引用的话,所有权会被转移到 `for` 语句块中,后面就无法再使用这个集合了)
```rust
for item in &container {
// ...
}
```
如果想在循环中,**修改该元素**,可以使用`mut`关键字:
如果想在循环中,**修改该元素**,可以使用 `mut` 关键字:
```rust
for item in &mut collection {
// ...
@ -127,20 +127,20 @@ for item in &mut collection {
```rust
fn main() {
let a = [4,3,2,1];
// `.iter()`方法把`a`数组变成一个迭代器
// `.iter()` 方法把 `a` 数组变成一个迭代器
for (i,v) in a.iter().enumerate() {
println!("第{}个元素是{}",i+1,v);
}
}
```
有同学可能会想到,如果我们想用`for`循环控制某个过程执行10次但是又不想单独声明一个变量来控制这个流程该怎么写
有同学可能会想到,如果我们想用 `for` 循环控制某个过程执行 10 次,但是又不想单独声明一个变量来控制这个流程,该怎么写?
```rust
for _ in 0..10 {
// ...
}
```
可以用`_`来替代`i`用于`for`循环中在Rust中`_`的含义是忽略该值或者类型的意思,如果不使用`_`,那么编译器会给你一个`变量未使用的`的警告。
可以用 `_` 来替代 `i` 用于 `for` 循环中,在 Rust `_` 的含义是忽略该值或者类型的意思,如果不使用 `_`,那么编译器会给你一个 `变量未使用的` 的警告。
**两种循环方式优劣对比**
@ -160,13 +160,13 @@ for item in collection {
```
第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环集合中的元素,优劣如下:
- **性能**:第一种使用方式中`collection[index]`的索引访问,会因为边界检查(bounds checking)导致运行时的性能损耗 - Rust会检查并确认`index`是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的
- **安全**:第一种方式里对`collection`的索引访问是非连续的,存在一定可能性在两次访问之间,`collection`发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险(这里是因为所有权吗?是的话可能要强调一下)
- **性能**:第一种使用方式中 `collection[index]` 的索引访问,会因为边界检查(Bounds Checking)导致运行时的性能损耗 —— Rust会检查并确认 `index` 是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的
- **安全**:第一种方式里对 `collection` 的索引访问是非连续的,存在一定可能性在两次访问之间,`collection` 发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险(这里是因为所有权吗?是的话可能要强调一下)
由于for循环无需任何条件限制也不需要通过索引来访问因此是最安全也是最常用的通过与下面的`while`的对比,我们能看到为什么`for`会更加安全。
由于 `for` 循环无需任何条件限制,也不需要通过索引来访问,因此是最安全也是最常用的,通过与下面的 `while` 的对比,我们能看到为什么 `for` 会更加安全。
#### `continue`
使用`continue`可以跳过当前当次的循环,开始下次的循环:
使用 `continue` 可以跳过当前当次的循环,开始下次的循环:
```rust
for i in 1..4 {
if i == 2 {
@ -175,7 +175,7 @@ for item in collection {
println!("{}",i);
}
```
上面代码对1到3的序列进行迭代且跳过值为2时的循环输出如下
上面代码对 1 3 的序列进行迭代,且跳过值为 2 时的循环,输出如下:
```console
1
3
@ -183,7 +183,7 @@ for item in collection {
#### while循环
如果你需要一个条件来循环,当该条件为`true`时,继续循环,条件为`false`,跳出循环,那么`while`就非常适用:
如果你需要一个条件来循环,当该条件为 `true` 时,继续循环,条件为 `false`,跳出循环,那么 `while` 就非常适用:
```rust
fn main() {
let mut n = 0;
@ -198,7 +198,7 @@ fn main() {
}
```
该`while`循环,只有当`n`小于等于`5`时,才执行,否则就立刻跳出循环,因此在上述代码中,它会先从`0`开始,满足条件,进行循环,然后是`1`,满足条件,进行循环,最终到`6`的时候大于5不满足条件跳出`while`循环,执行`我出来了`的打印,然后程序结束:
`while` 循环,只有当 `n` 小于等于 `5` 时,才执行,否则就立刻跳出循环,因此在上述代码中,它会先从 `0` 开始,满足条件,进行循环,然后是 `1`,满足条件,进行循环,最终到 `6` 的时候,大于 5不满足条件跳出 `while` 循环,执行 `我出来了` 的打印,然后程序结束:
```console
0!
1!
@ -209,7 +209,7 @@ fn main() {
我出来了!
```
当然,你也可以用其它方式组合实现,例如`loop`(无条件循环,将在下面介绍) + `if` + `break`
当然,你也可以用其它方式组合实现,例如 `loop`(无条件循环,将在下面介绍) + `if` + `break`
```rust
fn main() {
let mut n = 0;
@ -225,11 +225,11 @@ fn main() {
println!("我出来了!");
}
```
可以看出,在这种循环场景下,`while`要简洁的多。
可以看出,在这种循环场景下,`while` 要简洁的多。
**while vs for**
我们也能用`while`来实现`for`的功能:
我们也能用 `while` 来实现 `for` 的功能:
```rust
fn main() {
let a = [10, 20, 30, 40, 50];
@ -251,9 +251,9 @@ the value is: 40
the value is: 50
```
数组中的所有五个元素都如期被打印出来。尽管 index 在某一时刻会到达值 5不过循环在其尝试从数组获取第六个值会越界之前就停止了。
数组中的所有五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 5不过循环在其尝试从数组获取第六个值会越界之前就停止了。
但这个过程很容易出错;如果索引长度不正确会导致程序 panic。这也使程序更慢因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
但这个过程很容易出错;如果索引长度不正确会导致程序 ***panic***。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
`for`循环代码如下:
```rust
@ -266,13 +266,13 @@ fn main() {
}
```
可以看出,`for`并不会使用索引去访问数组,因此更安全也更简洁,同时避免`运行时的边界检查`,性能更高。
可以看出,`for` 并不会使用索引去访问数组,因此更安全也更简洁,同时避免 `运行时的边界检查`,性能更高。
#### loop循环
对于循环而言,`loop`循环毋庸置疑,是适用面最高的,它可以适用于所有循环场景(虽然能用,但是在很多场景下,`for`和`while`才是最优选择)因为loop就是一个简单的无限循环,你可以在内部实现逻辑通过`break`关键字来控制循环何时结束。
对于循环而言,`loop` 循环毋庸置疑,是适用面最高的,它可以适用于所有循环场景(虽然能用,但是在很多场景下, `for``while` 才是最优选择),因为 `loop` 就是一个简单的无限循环,你可以在内部实现逻辑通过 `break` 关键字来控制循环何时结束。
使用`loop`循环一定要打起精神,否则你会写出下面的跑满你一个cpu核心的疯子代码:
使用 `loop` 循环一定要打起精神,否则你会写出下面的跑满你一个 CPU 核心的疯子代码:
```rust,ignore
fn main() {
loop {
@ -281,7 +281,7 @@ fn main() {
}
```
该循环会不停的在终端打印输出,直到你使用`Ctrl-C`结束程序:
该循环会不停的在终端打印输出,直到你使用 `Ctrl-C` 结束程序:
```console
again!
again!
@ -292,7 +292,7 @@ again!
**注意**,不要轻易尝试上述代码,如果你电脑配置不行,可能会死机!!!
因此,当使用`loop`时,必不可少的伙伴是`break`关键字,它能让循环在满足某个条件时跳出:
因此,当使用 `loop` 时,必不可少的伙伴是 `break` 关键字,它能让循环在满足某个条件时跳出:
```rust
fn main() {
let mut counter = 0;
@ -308,9 +308,9 @@ fn main() {
println!("The result is {}", result);
}
```
以上代码当`counter`递增到`10`时,就会通过`break`返回一个`counter*2`的值,最后赋给`result`并打印出来。
以上代码当 `counter` 递增到 `10` 时,就会通过 `break` 返回一个 `counter * 2` 的值,最后赋给 `result` 并打印出来。
这里有几点值得注意:
- **break可以单独使用,也可以带一个返回值**,有些类似`return`
- **loop是一个表达式**,因此可以返回一个值
- **break 可以单独使用,也可以带一个返回值**,有些类似 `return`
- **loop 是一个表达式**,因此可以返回一个值

@ -19,7 +19,7 @@ match x {
### 匹配命名变量
在[match一章](./match-if-let#变量覆盖)中,我们有讲过变量覆盖的问题,这个在**匹配命名变量**时会遇到:
[match 一章](./match-if-let#变量覆盖)中,我们有讲过变量覆盖的问题,这个在**匹配命名变量**时会遇到:
```rust
fn main() {
@ -38,9 +38,9 @@ fn main() {
让我们看看当 `match` 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 `x` 中定义的值,所以代码继续执行。
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为这里的`y`在 `match` 表达式的作用域中,而不是之前`main`作用域中,所以这是一个新变量,不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会匹配任何 `Some` 中的值,在这里是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 5所以这个分支的表达式将会执行并打印出 `Matchedy = 5`
第二个匹配分支中的模式引入了一个新变量 `y`,它会匹配任何 `Some` 中的值。因为这里的 `y` `match` 表达式的作用域中,而不是之前 `main` 作用域中,所以这是一个新变量,不是开头声明为值 10 的那个 `y`。这个新的 `y` 绑定会匹配任何 `Some` 中的值,在这里是 `x` 中的值。因此这个 `y` 绑定了 `x``Some` 内部的值。这个值是 5所以这个分支的表达式将会执行并打印出 `Matchedy = 5`
如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配模式`_`。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被覆盖的 `x`,也就是`Some(5)`。
如果 `x` 的值是 `None` 而不是 `Some(5)`,头两个分支的模式不会匹配,所以会匹配模式 `_`。这个分支的模式中没有引入变量 `x`,所以此时表达式中的 `x` 会是外部没有被覆盖的 `x`,也就是 `Some(5)`
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`
@ -62,7 +62,7 @@ match x {
上面的代码会打印 `one or two`
### 通过序列`..=` 匹配值的范围
### 通过序列 `..=` 匹配值的范围
在[数值类型](../base-type/numbers#序列(Range))中我们有讲到一个序列语法,该语言不仅可以用循环中,还能用于匹配模式。
@ -77,7 +77,7 @@ match x {
}
```
如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1..=5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5`这五个值,而使用`..=`指定序列就简短的多,比如希望匹配比如从 1 到 1000 的数字的时候!
如果 `x` 是 1、2、3、4 或 5第一个分支就会匹配。这相比使用 `|` 运算符表达相同的意思更为方便;相比 `1..=5`,使用 `|` 则不得不指定 `1 | 2 | 3 | 4 | 5` 这五个值,而使用 `..=` 指定序列就简短的多,比如希望匹配比如从 1 到 1000 的数字的时候!
序列只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型。
@ -93,7 +93,7 @@ match x {
}
```
Rust 知道 `c` 位于第一个模式的序列内,所以会打印出 `early ASCII letter`
Rust 知道 `'c'` 位于第一个模式的序列内,所以会打印出 `early ASCII letter`
### 解构并分解值
@ -101,7 +101,7 @@ Rust 知道 `c` 位于第一个模式的序列内,所以会打印出 `early AS
#### 解构结构体
下面代码展示了如何用`let`解构一个带有两个字段 `x``y` 的结构体 `Point`
下面代码展示了如何用 `let` 解构一个带有两个字段 `x``y` 的结构体 `Point`
```rust
struct Point {
@ -120,7 +120,7 @@ fn main() {
这段代码创建了变量 `a``b` 来匹配结构体 `p` 中的 `x``y` 字段,这个例子展示了**模式中的变量名不必与结构体中的字段名一致**。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。
因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` 中`x` 和 `y`重复了,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。下例与上例有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
因为变量名匹配字段名是常见的,同时因为 `let Point { x: x, y: y } = p;` `x``y` 重复了,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。下例与上例有着相同行为的代码,不过 `let` 模式创建的变量为 `x``y` 而不是 `a``b`
```rust
struct Point {
@ -138,7 +138,7 @@ fn main() {
```
这段代码创建了变量 `x``y`,与结构体`p` 中的 `x``y`字段相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
这段代码创建了变量 `x``y`,与结构体 `p` 中的 `x``y` 字段相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
@ -160,15 +160,15 @@ fn main() {
}
```
首先是`match`第一个分支,指定匹配`y`为`0`的`Point`
然后第二个分支在第一个分支之后,匹配`y`不为`0``x`为`0`的`Point`;
最后一个分支匹配`x`不为`0``y`也不为`0`的`Point`。
首先是 `match` 第一个分支,指定匹配 `y` `0` `Point`
然后第二个分支在第一个分支之后,匹配 `y` 不为`0``x`为 `0` `Point`;
最后一个分支匹配 `x` 不为 `0``y` 也不为 `0` `Point`
在这个例子中,值 `p` 因为其 `x` 包含 0 而匹配第二个分支,因此会打印出 `On the y axis at 7`
#### 解构枚举
下面代码以`Message`枚举为例,编写一个 `match` 使用模式解构每一个内部值:
下面代码以 `Message` 枚举为例,编写一个 `match` 使用模式解构每一个内部值:
```rust
enum Message {
@ -205,7 +205,7 @@ fn main() {
}
```
这里老生常谈一句话,模式匹配一样要类型相同,因此匹配`Message::Move{1,2}`这样的枚举值,就必须要用`Message::Move{x,y}`这样的同类型模式才行。
这里老生常谈一句话,模式匹配一样要类型相同,因此匹配 `Message::Move{1,2}` 这样的枚举值,就必须要用 `Message::Move{x,y}` 这样的同类型模式才行。
这段代码会打印出 `Change the color to red 0, green 160, and blue 255`。尝试改变 `msg` 的值来观察其他分支代码的运行。
@ -215,7 +215,7 @@ fn main() {
#### 解构嵌套的结构体和枚举
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。 `match`也可以匹配嵌套的项!
目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。 `match` 也可以匹配嵌套的项!
例如使用下面的代码来同时支持 RGB 和 HSV 色彩模式:
@ -256,7 +256,7 @@ fn main() {
}
}
```
`match`第一个分支的模式匹配一个`Message::ChangeColor`枚举成员,该枚举成员又包含了一个`Color::Rgb`的枚举成员最终绑定了3个内部的`i32`值。第二个,就交给亲爱的读者来思考完成。
`match` 第一个分支的模式匹配一个 `Message::ChangeColor` 枚举成员,该枚举成员又包含了一个 `Color::Rgb` 的枚举成员,最终绑定了 3 个内部的 `i32` 值。第二个,就交给亲爱的读者来思考完成。
#### 解构结构体和元组
@ -275,7 +275,7 @@ let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
### 忽略模式中的值
有时忽略模式中的一些值是很有用的,比如在`match`中的最后一个分支使用`_`模式匹配所有剩余的值。 你也可以在另一个模式中使用 `_` 模式,使用一个以下划线开始的名称,或者使用 `..` 忽略所剩部分的值。
有时忽略模式中的一些值是很有用的,比如在 `match` 中的最后一个分支使用 `_` 模式匹配所有剩余的值。 你也可以在另一个模式中使用 `_` 模式,使用一个以下划线开始的名称,或者使用 `..` 忽略所剩部分的值。
#### 使用 `_` 忽略整个值
@ -298,7 +298,7 @@ fn main() {
#### 使用嵌套的 `_` 忽略部分值
可以在一个模式内部使用`_` 忽略部分值:
可以在一个模式内部使用 `_` 忽略部分值:
```rust
let mut setting_value = Some(5);
@ -319,8 +319,8 @@ println!("setting is {:?}", setting_value);
这段代码会打印出 `Can't overwrite an existing customized value` 接着是 `setting is Some(5)`
第一个匹配分支,我们不关心里面的值,只关心元组中两个元素的类型,因此对于`Some`中的值,直接进行忽略。
剩下的形如`(Some(_),None)``(None, Some(_))`, `(None,None)`形式,都由第二个分支`_`进行分配。
第一个匹配分支,我们不关心里面的值,只关心元组中两个元素的类型,因此对于 `Some` 中的值,直接进行忽略。
剩下的形如 `(Some(_),None)``(None, Some(_))`, `(None,None)` 形式,都由第二个分支 `_` 进行分配。
还可以在一个模式中的多处使用下划线来忽略特定值,如下所示,这里忽略了一个五元元组中的第二和第四个值:
@ -335,13 +335,13 @@ match numbers {
}
```
老生常谈:模式匹配一定要类型相同,因此匹配`numbers`元组的模式,也必须有五个值(元组中元素的数量也属于元组类型的一部分)。
老生常谈:模式匹配一定要类型相同,因此匹配 `numbers` 元组的模式,也必须有五个值(元组中元素的数量也属于元组类型的一部分)。
这会打印出 `Some numbers: 2, 8, 32`, 值 4 和 16 会被忽略。
#### 使用下划线开头忽略未使用的变量
如果你创建了一个变量却不在任何地方使用它Rust 通常会给你一个警告,因为这可能会是个bug。但是有时创建一个不会被使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头:
如果你创建了一个变量却不在任何地方使用它Rust 通常会给你一个警告,因为这可能会是个 BUG。但是有时创建一个不会被使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头:
```rust
fn main() {
@ -350,7 +350,7 @@ fn main() {
}
```
这里得到了警告说未使用变量 `y`,至于`x`则没有警告。
这里得到了警告说未使用变量 `y`,至于 `x` 则没有警告。
注意, 只使用 `_` 和使用以下划线开头的名称有些微妙的不同:比如 **`_x` 仍会将值绑定到变量,而 `_` 则完全不会绑定**。
@ -363,7 +363,7 @@ if let Some(_s) = s {
println!("{:?}", s);
```
`s`是一个拥有所有权的动态字符串,在上面代码中,我们会得到一个错误,因为 `s` 的值会被转移给 `_s`,在`println!`中再次使用`s`会报错:
`s` 是一个拥有所有权的动态字符串,在上面代码中,我们会得到一个错误,因为 `s` 的值会被转移给 `_s`,在 `println!` 中再次使用 `s` 会报错:
```console
error[E0382]: borrow of partially moved value: `s`
--> src/main.rs:8:22
@ -389,7 +389,7 @@ println!("{:?}", s);
#### 用 `..` 忽略剩余值
对于有多个部分的值,可以使用 `..` 语法来只使用部分值而忽略其它值,这样也不用再为每一个被忽略的值都单独列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分.
对于有多个部分的值,可以使用 `..` 语法来只使用部分值而忽略其它值,这样也不用再为每一个被忽略的值都单独列出下划线。`..` 模式会忽略模式中剩余的任何没有显式匹配的值部分
```rust
struct Point {
@ -405,9 +405,9 @@ match origin {
}
```
这里列出了 `x` 值,接着使用了`..` 模式来忽略其它字段,这样的写法要比一一列出其它字段,然后用`_`忽略简洁的多。
这里列出了 `x` 值,接着使用了 `..` 模式来忽略其它字段,这样的写法要比一一列出其它字段,然后用 `_` 忽略简洁的多。
还可以用`..`来忽略元组中间的某些值:
还可以用 `..` 来忽略元组中间的某些值:
```rust
fn main() {
@ -440,7 +440,7 @@ fn main() {
如果编译上面的例子,会得到下面的错误:
```text
error: `..` can only be used once per tuple pattern // 每个元组模式只能使用一个`..`
error: `..` can only be used once per tuple pattern // 每个元组模式只能使用一个 `..`
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
@ -451,7 +451,7 @@ error: `..` can only be used once per tuple pattern // 每个元组模式只能
error: could not compile `world_hello` due to previous error ^^
```
Rust无法判断`second`应该匹配`numbers`中的第几个元素,因此这里使用两个`..`模式,是由很大歧义的!
Rust无法判断`second` 应该匹配 `numbers` 中的第几个元素,因此这里使用两个 `..` 模式,是由很大歧义的!
### 匹配守卫提供的额外条件
@ -470,11 +470,11 @@ match num {
}
```
这个例子会打印出 `less than five: 4`。当 `num` 与模式中第一个分支匹配时,`Some(4)` 可以与 `Some(x)`匹配,接着匹配守卫检查 `x` 值是否小于 `5`,因为 `4` 小于 `5`,所以第一个分支被选择。
这个例子会打印出 `less than five: 4`。当 `num` 与模式中第一个分支匹配时,`Some(4)` 可以与 `Some(x)` 匹配,接着匹配守卫检查 `x` 值是否小于 5因为 4 小于 5所以第一个分支被选择。
相反如果 `num``Some(10)`,因为 10 不小于 5 ,所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,因为这里没有匹配守卫所以会匹配任何 `Some` 成员。
模式中无法提供类如`if x < 5`
模式中无法提供类如 `if x < 5` 的表达能力,我们可以通过匹配守卫的方式来实现。
在之前,我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 `match` 表达式的模式中新建了一个变量而不是使用 `match` 之外的同名变量。内部变量覆盖了外部变量,意味着此时不能够使用外部变量的值,下面代码展示了如何使用匹配守卫修复这个问题。
@ -497,7 +497,7 @@ fn main() {
匹配守卫 `if n == y` 并不是一个模式所以没有引入新变量。这个 `y` **正是** 外部的 `y` 而不是新的覆盖变量 `y`,这样就可以通过比较 `n``y` 来表达寻找一个与外部 `y` 相同的值的概念了。
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,**同时匹配守卫的条件会作用于所有的模式**。下面代码展示了匹配守卫与 `|` 的优先级。这个例子中看起来好像 `if y` 只作用于 `6`,但实际上匹配守卫 `if y` 作用于 `4`、`5` **和** `6` ,在满足`x`属于 `4 | 5 | 6` 后才会判断 `y` 是否为 `true`
也可以在匹配守卫中使用 **或** 运算符 `|` 来指定多个模式,**同时匹配守卫的条件会作用于所有的模式**。下面代码展示了匹配守卫与 `|` 的优先级。这个例子中看起来好像 `if y` 只作用于 `6`,但实际上匹配守卫 `if y` 作用于 `4`、`5` **和** `6` ,在满足 `x` 属于 `4 | 5 | 6` 后才会判断 `y` 是否为 `true`
```rust
let x = 4;
@ -553,16 +553,16 @@ match msg {
上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时将该值绑定到变量`id_variable`上。
上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时将该值绑定到变量 `id_variable` 上。
第二个分支只在模式中指定了一个范围,`id` 字段的值可以是 `10、11 或 12`,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。
当你既想要限定分支范围,又想要使用分支的变量时,就可以用`@`来绑定到一个新的变量上,实现想要的功能。
当你既想要限定分支范围,又想要使用分支的变量时,就可以用 `@` 来绑定到一个新的变量上,实现想要的功能。
#### @前绑定后解构(Rust1.56新增)
使用`@`还可以在绑定新变量的同时,对目标进行解构:
#### @前绑定后解构(Rust 1.56 新增)
使用 `@` 还可以在绑定新变量的同时,对目标进行解构:
```rust
#[derive(Debug)]
struct Point {
@ -571,7 +571,7 @@ struct Point {
}
fn main() {
// 绑定新变量`p`,同时对`Point`进行解构
// 绑定新变量 `p`,同时对 `Point` 进行解构
let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
println!("x: {}, y: {}", px, py);
println!("{:?}", p);
@ -586,7 +586,7 @@ fn main() {
}
```
#### @新特性(Rust1.53新增)
#### @新特性(Rust 1.53 新增)
考虑下面一段代码:
```rust
fn main() {
@ -598,12 +598,12 @@ fn main() {
}
}
```
编译不通过,是因为`num`没有绑定到所有的模式上,只绑定了模式`1`,你可能会试图通过这个方式来解决:
编译不通过,是因为 `num` 没有绑定到所有的模式上,只绑定了模式 `1`,你可能会试图通过这个方式来解决:
```rust
num @ (1 | 2)
```
但是如果你用的是Rust1.53之前的版本,那这种写法会报错,因为编译器不支持。
但是,如果你用的是 Rust 1.53 之前的版本,那这种写法会报错,因为编译器不支持。
至此,模式匹配的内容已经全部完结,复杂但是详尽,想要一次性全部记住属实不易,因此读者可以先留一个印象,等未来需要时,再来翻阅寻找具体的模式实现方式。

@ -2,5 +2,5 @@
模式匹配,这个词,对于非函数语言编程来说,真的还蛮少听到,因为它经常出现在函数式编程里,用于为复杂的类型系统提供一个轻松的解构能力。
曾记否?在枚举和流程控制那章,我们遗留了两个问题,都是关于`match`的,第一个是如何对`Option`枚举进行进一步处理,另外一个就是如何用`match`来替代`else if`这种丑陋的多重分支使用方式,那么让我们先一起来揭开`match`的神秘面纱。
曾记否?在枚举和流程控制那章,我们遗留了两个问题,都是关于 `match` 的,第一个是如何对 `Option` 枚举进行进一步处理,另外一个就是如何用 `match` 来替代 `else if` 这种丑陋的多重分支使用方式,那么让我们先一起来揭开 `match` 的神秘面纱。

@ -1,8 +1,8 @@
# match和if let
在Rust中模式匹配最常用的就是`match`和`if let`,本章节将对两者及相关的概念进行详尽介绍。
Rust 中,模式匹配最常用的就是 `match` `if let`,本章节将对两者及相关的概念进行详尽介绍。
先来看一个关于`match`的简单例子:
先来看一个关于 `match` 的简单例子:
```rust
enum Direction {
East,
@ -23,17 +23,17 @@ fn main() {
}
```
这里我们想去匹配`dire`对应的枚举类型因此在match中用三个匹配分支来完全覆盖枚举变量`Direction`的所有成员类型,有以下几点值得注意:
- `match`的匹配必须要穷举出所有可能,因此这里用`_`来代表未列出的所有可能性
- `match`的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
- **X | Y**,是逻辑运算符`或`,代表该分支可以匹配`X`也可以匹配`Y`,只要满足一个即可
这里我们想去匹配 `dire` 对应的枚举类型,因此在 `match` 中用三个匹配分支来完全覆盖枚举变量 `Direction` 的所有成员类型,有以下几点值得注意:
- `match` 的匹配必须要穷举出所有可能,因此这里用 `_` 来代表未列出的所有可能性
- `match` 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
- **X | Y**,是逻辑运算符 `或`,代表该分支可以匹配 `X` 也可以匹配 `Y`,只要满足一个即可
其实`match`跟其他语言中的`switch`非常像,`_`类似于`switch`中的`default`。
其实 `match` 跟其他语言中的 `switch` 非常像,`_` 类似于 `switch` 中的 `default`
## `match`匹配
## `match` 匹配
首先来看看`match`的通用形式:
首先来看看 `match` 的通用形式:
```rust
match target {
模式1 => 表达式1,
@ -46,9 +46,9 @@ match target {
}
```
该形式清晰的说明了何为模式,何为模式匹配:将模式与`target`进行匹配,即为模式匹配,而模式匹配不仅仅局限于`match`,后面我们会详细阐述。
该形式清晰的说明了何为模式,何为模式匹配:将模式与 `target` 进行匹配,即为模式匹配,而模式匹配不仅仅局限于 `match`,后面我们会详细阐述。
`match`允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行对应的代码,下面让我们来一一详解,先看一个例子:
`match` 允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行对应的代码,下面让我们来一一详解,先看一个例子:
```rust
enum Coin {
@ -71,16 +71,16 @@ fn value_in_cents(coin: Coin) -> u8 {
}
```
`value_in_cents`函数根据匹配到的硬币,返回对应的美分数值`match`后紧跟着的是一个表达式,跟`if`很像,但是`if`后的表达式必须是一个布尔值,而`match`后的表达式返回值可以是任意类型,只要能跟后面的分支中的模式匹配起来即可,这里的`coin`是枚举`Coin`类型。
`value_in_cents` 函数根据匹配到的硬币,返回对应的美分数值。`match` 后紧跟着的是一个表达式,跟 `if` 很像,但是 `if` 后的表达式必须是一个布尔值,而 `match` 后的表达式返回值可以是任意类型,只要能跟后面的分支中的模式匹配起来即可,这里的 `coin` 是枚举 `Coin` 类型。
接下来是`match`的分支。一个分支有两个部分:**一个模式和针对该模式的处理代码**。第一个分支的模式是`Coin::Penny`,其后的`=>`运算符将模式和将要运行的代码分开。这里的代码就仅仅是表达式`1`,不同分支之间使用逗号分隔。
接下来是 `match` 的分支。一个分支有两个部分:**一个模式和针对该模式的处理代码**。第一个分支的模式是 `Coin::Penny`,其后的 `=>` 运算符将模式和将要运行的代码分开。这里的代码就仅仅是表达式 `1`,不同分支之间使用逗号分隔。
当`match`表达式执行时,它将目标值`coin`按顺序依次与每一个分支的模式相比较,如果模式匹配了这个值,那么模式之后的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。
`match` 表达式执行时,它将目标值 `coin` 按顺序依次与每一个分支的模式相比较,如果模式匹配了这个值,那么模式之后的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。如果分支有多行代码,那么需要用`{}`包裹,同时最后一行代码需要是一个表达式。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 `match` 表达式的返回值。如果分支有多行代码,那么需要用 `{}` 包裹,同时最后一行代码需要是一个表达式。
#### 使用`match`表达式赋值
还有一点很重要,`match`本身也是一个表达式,因此可以用它来赋值:
#### 使用 `match` 表达式赋值
还有一点很重要,`match` 本身也是一个表达式,因此可以用它来赋值:
```rust
enum IpAddr {
Ipv4,
@ -98,7 +98,7 @@ fn main() {
println!("{}", ip_str);
}
```
因为这里匹配到`_`分支,所以将`"::1"`赋值给了`ip_str`.
因为这里匹配到 `_` 分支,所以将 `"::1"` 赋值给了 `ip_str`
#### 模式绑定
@ -118,9 +118,9 @@ enum Coin {
Quarter(UsState), // 25美分硬币
}
```
其中`Coin::Quarter`成员还存放了一个值美国的某个州因为在1999年到2008年间美国在25美分(Quarter)硬币的背后为50个州印刷了不同的标记其它硬币都没有这样的设计
其中 `Coin::Quarter` 成员还存放了一个值:美国的某个州(因为在 1999 年到 2008 年间,美国在 25 美分(Quarter)硬币的背后为 50 个州印刷了不同的标记,其它硬币都没有这样的设计)。
接下来我们希望在模式匹配中获取到25美分硬币上刻印的州的名称
接下来,我们希望在模式匹配中,获取到 25 美分硬币上刻印的州的名称:
```rust
fn value_in_cents(coin: Coin) -> u8 {
match coin {
@ -134,9 +134,9 @@ fn value_in_cents(coin: Coin) -> u8 {
}
}
```
上面代码中,在匹配`Coin::Quarter(state)`模式时,我们把它内部存储的值绑定到了`state`变量上,因此`state`变量就是对应的`UsState`枚举类型。
上面代码中,在匹配 `Coin::Quarter(state)` 模式时,我们把它内部存储的值绑定到了 `state` 变量上,因此 `state` 变量就是对应的 `UsState` 枚举类型。
例如有一个印了阿拉斯加州标记的25分硬币`Coin::Quarter(UsState::Alaska))`, 它在匹配时,`state`变量将被绑定`UsState::Alaska`的枚举值。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska))`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
再来看一个更复杂的例子:
```rust
@ -182,7 +182,7 @@ change color into '(r:255, g:255, b:0)', 'b' has been ignored
```
#### 穷尽匹配
在文章的开头,我们简单总结过`match`的匹配必须穷尽所有情况,下面来举例说明,例如:
在文章的开头,我们简单总结过 `match` 的匹配必须穷尽所有情况,下面来举例说明,例如:
```rust
enum Direction {
East,
@ -202,9 +202,9 @@ fn main() {
}
```
我们没有处理`Direction::West`的情况,因此会报错:
我们没有处理 `Direction::West` 的情况,因此会报错:
```console
error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`West`没有被覆盖
error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`West` 没有被覆盖
--> src/main.rs:10:11
|
1 | / enum Direction {
@ -217,17 +217,17 @@ error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`
| |_- `Direction` defined here
...
10 | match dire {
| ^^^^ pattern `West` not covered // 模式`West`没有被覆盖
| ^^^^ pattern `West` not covered // 模式 `West` 没有被覆盖
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Direction`
```
不禁想感叹,`Rust`的编译器真**强大忍不住爆粗口了sorry如果你以后进一步深入使用Rust也会像我这样感叹的。Rust编译器清晰地知道`match`中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性,有效避免传说中价值十亿美金的`null`陷阱。
不禁想感叹,Rust 的编译器**真强大**忍不住爆粗口了sorry如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金** `null` 陷阱。
#### `_` 通配符
当我们不想在匹配的时候列出所有值的时候可以使用Rust提供的一个特殊**模式**,例如,`u8` 可以拥有 0 到 255 的有效的值,但是我们只关心 `1、3、5 和 7` 这几个值,不想列出其它的 `0、2、4、6、8、9 一直到 255` 的值。那么, 我们不必一个一个列出所有值, 因为可以使用使用特殊的模式 `_` 替代:
当我们不想在匹配的时候列出所有值的时候,可以使用 Rust 提供的一个特殊**模式**,例如,`u8` 可以拥有 0 到 255 的有效的值,但是我们只关心 `1、3、5 和 7` 这几个值,不想列出其它的 `0、2、4、6、8、9 一直到 255` 的值。那么, 我们不必一个一个列出所有值, 因为可以使用使用特殊的模式 `_` 替代:
```rust
let some_u8_value = 0u8;
@ -240,12 +240,12 @@ match some_u8_value {
}
```
通过将`_`其放置于其他分支后,`_`将会匹配所有遗漏的值。`()`表示啥都不做的意思,所以当匹配到`_`后,什么也不会发生。
通过将 `_` 其放置于其他分支后,`_` 将会匹配所有遗漏的值。`()` 表示啥都不做的意思,所以当匹配到 `_` 后,什么也不会发生。
然后,在某些场景下,我们其实只关心**某一个值是否存在**,此时`match`就显得过于啰嗦。
然后,在某些场景下,我们其实只关心**某一个值是否存在**,此时 `match` 就显得过于啰嗦。
## `if let`匹配
有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用`match`来处理就要写成下面这样:
## `if let` 匹配
有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 `match` 来处理就要写成下面这样:
```rust
let v = Some(3u8);
match v{
@ -254,19 +254,19 @@ match some_u8_value {
}
````
我们只想要对 `Some(3)` 模式进行匹配, 不想处理任何其他 `Some<u8>` 值或 `None` 值。但是为了满足`match`表达式(穷尽性)的要求,写代码时必须在处理完这唯一的成员后加上 `_ => ()`,这样会增加不少无用的代码。
我们只想要对 `Some(3)` 模式进行匹配, 不想处理任何其他 `Some<u8>` 值或 `None` 值。但是为了满足 `match` 表达式(穷尽性)的要求,写代码时必须在处理完这唯一的成员后加上 `_ => ()`,这样会增加不少无用的代码。
杀鸡焉用牛刀,可以用`if let`的方式来实现:
杀鸡焉用牛刀,可以用 `if let` 的方式来实现:
```rust
if let Some(3) = some_u8_value {
println!("three");
}
```
这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好:**当你只要匹配一个条件,且忽略其他条件时就用`if let`否则都用match**。
这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好:**当你只要匹配一个条件,且忽略其他条件时就用 `if let` ,否则都用 `match`**。
## matches!宏
Rust标准库中提供了一个非常实用的宏:`matches!`,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果`true` or `false`
Rust 标准库中提供了一个非常实用的宏:`matches!`,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 `true` or `false`
例如,有一个动态数组,里面存有以下枚举:
```rust
@ -280,12 +280,12 @@ fn main() {
}
```
现在如果想对`v`进行过滤,只保留类型是`MyEnum::Foo`的元素,你可能想这么写:
现在如果想对 `v` 进行过滤,只保留类型是 `MyEnum::Foo` 的元素,你可能想这么写:
```rust
v.iter().filter(|x| x == MyEnum::Foo);
```
但是,实际上这行代码会报错,因为你无法将`x`直接跟一个枚举成员进行比较。好在,你可以使用`match`来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用`matches!`:
但是,实际上这行代码会报错,因为你无法将 `x` 直接跟一个枚举成员进行比较。好在,你可以使用 `match` 来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用 `matches!`
```rust
v.iter().filter(|x| matches!(x, MyEnum::Foo));
```
@ -300,7 +300,7 @@ assert!(matches!(bar, Some(x) if x > 2));
```
## 变量覆盖
无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:
无论是是 `match` 还是 `if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:
```rust
fn main() {
let age = Some(30);
@ -313,16 +313,16 @@ fn main() {
}
```
`cargo run`运行后输出如下:
`cargo run `运行后输出如下:
```console
在匹配前age是Some(30)
匹配出来的age是30
在匹配后age是Some(30)
```
可以看出在`if let`中,`=`右边`Some(i32)`类型的`age`被左边`i32`类型的新`age`覆盖了,该覆盖一直持续到`if let`语句块的结束。因此第三个`println!`输出的`age`依然是`Some(i32)`类型。
可以看出在 `if let` 中,`=` 右边 `Some(i32)` 类型的 `age` 被左边 `i32` 类型的新 `age` 覆盖了,该覆盖一直持续到 `if let` 语句块的结束。因此第三个 `println!` 输出的 `age` 依然是 `Some(i32)` 类型。
对于`match`类型也是如此:
对于 `match` 类型也是如此:
```rust
fn main() {
let age = Some(30);
@ -335,4 +335,4 @@ fn main() {
}
```
需要注意的是,**`match`中的变量覆盖其实不是那么的容易看出**,因此要小心!
需要注意的是,**`match` 中的变量覆盖其实不是那么的容易看出**,因此要小心!

@ -1,21 +1,21 @@
# 解构Option
在枚举那章,提到过`Option`枚举它用来解决Rust中变量是否有值的问题定义如下
在枚举那章,提到过 `Option` 枚举,它用来解决 Rust 中变量是否有值的问题,定义如下:
```rust
enum Option<T> {
Some(T),
None,
}
```
简单解释就是: **一个变量要么有值:`Some(T)`, 要么为空: `None`**.
简单解释就是: **一个变量要么有值`Some(T)`, 要么为空:`None`**。
那么现在的问题就是该如何去使用这个`Option`枚举类型,根据我们上一节的经验,可以通过`match`来实现。
那么现在的问题就是该如何去使用这个 `Option` 枚举类型,根据我们上一节的经验,可以通过 `match` 来实现。
> 因为`Option``Some``None`都包含在`prelude`中,因此你可以直接通过名称来使用它们,而无需以`Option::Some`这种形式去使用,总之,千万不要因为调用路径变短了,就忘记`Some`和`None`也是`Option`底下的枚举成员!
> 因为 `Option``Some``None` 都包含在 `prelude` 中,因此你可以直接通过名称来使用它们,而无需以 `Option::Some` 这种形式去使用,总之,千万不要因为调用路径变短了,就忘记 `Some` `None` 也是 `Option` 底下的枚举成员!
## 匹配 `Option<T>`
使用`Option<T>`,是为了从 `Some` 中取出其内部的 `T` 值以及处理没有值的情况,为了演示这一点,下面一起来编写一个函数,它获取一个 `Option<i32>`,如果其中含有一个值,将其加一;如果其中没有值,则函数返回 `None` 值:
使用 `Option<T>`,是为了从 `Some` 中取出其内部的 `T` 值以及处理没有值的情况,为了演示这一点,下面一起来编写一个函数,它获取一个 `Option<i32>`,如果其中含有一个值,将其加一;如果其中没有值,则函数返回 `None` 值:
```rust
fn plus_one(x: Option<i32>) -> Option<i32> {
@ -30,16 +30,16 @@ let six = plus_one(five);
let none = plus_one(None);
```
`plus_one`接受一个`Option<i32>`类型的参数,同时返回一个`Option<i32>`类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个`None`,则返回一个`None`且不做任何处理;如果传入的是一个`Some(i32)`,则通过模式绑定,把其中的值绑定到变量`i`上,然后返回`i+1`的值,同时用`Some`进行包裹。
`plus_one` 接受一个 `Option<i32>` 类型的参数,同时返回一个 `Option<i32>` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。
为了进一步说明,假设`plus_one`函数接受的参数值x是`Some(5)`,来看看具体的分支匹配情况:
为了进一步说明,假设 `plus_one` 函数接受的参数值x是 `Some(5)`,来看看具体的分支匹配情况:
#### 传入参数`Some(5)`
#### 传入参数 `Some(5)`
```rust,ignore
None => None,
```
首先是匹配`None`分支,因为值`Some(5)` 并不匹配模式 `None`,所以继续匹配下一个分支。
首先是匹配 `None` 分支,因为值 `Some(5)` 并不匹配模式 `None`,所以继续匹配下一个分支。
```rust,ignore
Some(i) => Some(i + 1),
@ -48,11 +48,11 @@ Some(i) => Some(i + 1),
`Some(5)``Some(i)` 匹配吗?当然匹配!它们是相同的成员。`i` 绑定了 `Some` 中包含的值,因此 `i` 的值是 `5`。接着匹配分支的代码被执行,最后将 `i` 的值加一并返回一个含有值 `6` 的新 `Some`
#### 传入参数None
接着考虑下`plus_one` 的第二个调用,这次传入的`x` 是`None` 我们进入 `match` 并与第一个分支相比较。
接着考虑下 `plus_one` 的第二个调用,这次传入的 `x` `None` 我们进入 `match` 并与第一个分支相比较。
```rust,ignore
None => None,
```
匹配上了!接着程序继续执行该分支后的代码:返回表达式`None`的值,也就是返回一个`None`,因为第一个分支就匹配到了,其他的分支将不再比较。
匹配上了!接着程序继续执行该分支后的代码:返回表达式 `None` 的值,也就是返回一个 `None`,因为第一个分支就匹配到了,其他的分支将不再比较。

@ -1,7 +1,7 @@
# 模式适用场景
## 模式
模式是Rust中的特殊语法它用来匹配类型中的结构和数据它往往和`match`表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 `match` 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
- 字面值
- 解构的数组、枚举、结构体或者元组
- 变量
@ -20,7 +20,7 @@ match VALUE {
PATTERN => EXPRESSION,
}
```
如上所示,`match`的每个分支就是一个**模式**,因为`match`匹配是穷尽式的,因此我们往往需要一个特殊的模式`_`,来匹配剩余的所有情况:
如上所示,`match` 的每个分支就是一个**模式**,因为 `match` 匹配是穷尽式的,因此我们往往需要一个特殊的模式 `_`,来匹配剩余的所有情况:
```rust
match VALUE {
PATTERN => EXPRESSION,
@ -30,7 +30,7 @@ match VALUE {
```
#### if let分支
`if let`往往用于匹配一个模式,而忽略剩下的所有模式的场景:
`if let` 往往用于匹配一个模式,而忽略剩下的所有模式的场景:
```rust
if let PATTERN = SOME_VALUE {
@ -38,7 +38,7 @@ if let PATTERN = SOME_VALUE {
```
#### while let条件循环
一个与 `if let` 类似的结构是 `while let` 条件循环,它允许只要模式匹配就一直进行 `while` 循环。下面展示了一个使用`while let`的例子:
一个与 `if let` 类似的结构是 `while let` 条件循环,它允许只要模式匹配就一直进行 `while` 循环。下面展示了一个使用 `while let` 的例子:
```rust
// Vec是动态数组
let mut stack = Vec::new();
@ -54,9 +54,9 @@ while let Some(top) = stack.pop() {
}
```
这个例子会打印出 `3`、`2` 接着是 `1`。`pop` 方法取出动态数组的最后一个元素并返回 `Some(value)`,如果动态数组是空的,将返回 `None`,对于`while`来说,只要 `pop` 返回 `Some` 就会一直不停的循环。一旦其返回 `None``while` 循环停止。我们可以使用 `while let` 来弹出栈中的每一个元素。
这个例子会打印出 `3`、`2` 接着是 `1`。`pop` 方法取出动态数组的最后一个元素并返回 `Some(value)`,如果动态数组是空的,将返回 `None`,对于 `while` 来说,只要 `pop` 返回 `Some` 就会一直不停的循环。一旦其返回 `None``while` 循环停止。我们可以使用 `while let` 来弹出栈中的每一个元素。
你也可以用`loop` + `if let` 或者`match`来实现这个功能,但是会更加啰嗦。
你也可以用 `loop` + `if let` 或者 `match` 来实现这个功能,但是会更加啰嗦。
#### for循环
```rust
@ -67,7 +67,7 @@ for (index, value) in v.iter().enumerate() {
}
```
这里使用 `enumerate` 方法产生一个迭代器,该迭代器每次迭代会返回一个`(索引,值)`形式的元组,然后用`(index,value)`来匹配。
这里使用 `enumerate` 方法产生一个迭代器,该迭代器每次迭代会返回一个 `(索引,值)` 形式的元组,然后用 `(index,value)` 来匹配。
#### let语句
@ -78,13 +78,13 @@ let PATTERN = EXPRESSION;
```rust
let x = 5;
```
这其中,`x`也是一种模式绑定,代表将**匹配的值绑定到变量x上**。因此在Rust中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量x上**。因此,在 Rust 中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
```rust
let (x, y, z) = (1, 2, 3);
```
上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把`1,2,3`分别绑定到`x,y,z`上。
上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。
模式匹配要求两边的类型必须相同,否则就会导致下面的报错:
```rust
@ -113,7 +113,7 @@ fn foo(x: i32) {
// 代码
}
```
其中`x`就是一个模式,你还可以在参数中匹配元组:
其中 `x` 就是一个模式,你还可以在参数中匹配元组:
```rust
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
@ -124,7 +124,7 @@ fn main() {
print_coordinates(&point);
}
```
`&(3,5)`会匹配模式`&(x,y)`,因此`x`得到了`3``y`得到了`5`.
`&(3, 5)` 会匹配模式 `&(x, y)`,因此 `x` 得到了 `3``y` 得到了 `5`
#### if 和 if let
@ -133,15 +133,15 @@ fn main() {
```rust
let Some(x) = some_option_value;
```
因为右边的值可能不为`Some`,而是`None`,这种时候就不能进行匹配,也就是上面的代码遗漏了`None`的匹配。
因为右边的值可能不为 `Some`,而是 `None`,这种时候就不能进行匹配,也就是上面的代码遗漏了 `None` 的匹配。
类似`let`和`for`、`match`都必须要求完全覆盖匹配,才能通过编译。
类似 `let` `for`、`match` 都必须要求完全覆盖匹配,才能通过编译。
但是对于`if let`,就可以这样使用:
但是对于 `if let`,就可以这样使用:
```rust
if let Some(x) = some_option_value {
println!("{}", x);
}
```
因为`if let`允许匹配一种模式,而忽略其余的模式。
因为 `if let` 允许匹配一种模式,而忽略其余的模式。

Loading…
Cancel
Save