1.5 出栈
原文链接: https://rust-unofficial.github.io/too-many-lists/first-pop.html
和 push 一样,pop 要修改链表。与 push 不同,我们实际上要返回点什么。但 pop 还要处理一个棘手的边界情况:链表为空怎么办?为此我们用可靠的 Option 类型:
1
2
3
| pub fn pop(&mut self) -> Option<i32> {
// TODO
}
|
Option<T> 是一个枚举,表示某个值可能存在。可以是 Some(T) 或 None。我们可以像对 Link 那样自己写枚举,但我们希望用户能看懂返回类型是什么,而 Option 太常见了,每个人都认识。事实上它如此基础,以至于每个文件里都会隐式导入,连同它的变体 Some 和 None(所以我们不必写 Option::None)。
Option<T> 上的尖括号表示 Option 在 T 上是泛型的。也就是说你可以为任何类型做 Option!
那么,我们有这个 Link,怎么判断是 Empty 还是有 More?用 match 做模式匹配!
1
2
3
4
5
6
7
8
9
10
| pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| > cargo build
error[E0308]: mismatched types
--> src/first.rs:27:30
|
27 | pub fn pop(&mut self) -> Option<i32> {
| --- ^^^^^^^^^^^ expected enum `std::option::Option`, found ()
| |
| this function's body doesn't return
|
= note: expected type `std::option::Option<i32>`
found type `()`
|
哎呀,pop 必须返回值,我们还没做。我们可以返回 None,但这里用 unimplemented!() 可能更好,表示函数还没实现完。unimplemented!() 是一个宏(! 表示宏),执行到它时会让程序 panic(以受控方式~崩溃)。
1
2
3
4
5
6
7
8
9
10
11
| pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
unimplemented!()
}
|
无条件 panic 是发散函数的例子。发散函数永远不会返回给调用者,所以可以用在期望任意类型值的地方。这里 unimplemented!() 被用在 Option<T> 类型的值的位置上。
还要注意程序里不必写 return。函数里最后一条表达式(基本上就是最后一行)隐式成为返回值。这让很简单的事写得更简洁。你仍然可以像其他类 C 语言一样用 return 提前返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| > cargo build
error[E0507]: cannot move out of borrowed content
--> src/first.rs:28:15
|
28 | match self.head {
| ^^^^^^^^^
| |
| cannot move out of borrowed content
| help: consider borrowing here: `&self.head`
...
32 | Link::More(node) => {
| ---- data moved here
|
note: move occurs because `node` has type `std::boxed::Box<first::Node>`, which does not implement the `Copy` trait
--> src/first.rs:32:24
|
32 | Link::More(node) => {
| ^^^^
|
拜托 Rust,别烦我们!一如既往,Rust 对我们大发雷霆。幸好这次也把底细说全了!默认情况下,模式匹配会尝试把内容移入新分支,但我们做不到,因为这里不是按值拥有 self。
1
| help: consider borrowing here: `&self.head`
|
Rust 说应该在 match 上加引用来修。🤷♀️ 试试:
1
2
3
4
5
6
7
8
9
10
11
| pub fn pop(&mut self) -> Option<i32> {
match &self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
unimplemented!()
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| > cargo build
warning: unused variable: `node`
--> src/first.rs:32:24
|
32 | Link::More(node) => {
| ^^^^ help: consider prefixing with an underscore: `_node`
|
= note: #[warn(unused_variables)] on by default
warning: field is never used: `elem`
--> src/first.rs:13:5
|
13 | elem: i32,
| ^^^^^^^^^
|
= note: #[warn(dead_code)] on by default
warning: field is never used: `next`
--> src/first.rs:14:5
|
14 | next: Link,
| ^^^^^^^^^^
|
好耶,又能编译了!现在理清逻辑。我们要做一个 Option,先建个变量。Empty 时返回 None。More 时返回 Some(i32),并改链表 head。那就基本上这么做?
1
2
3
4
5
6
7
8
9
10
11
12
13
| pub fn pop(&mut self) -> Option<i32> {
let result;
match &self.head {
Link::Empty => {
result = None;
}
Link::More(node) => {
result = Some(node.elem);
self.head = node.next;
}
};
result
}
|
1
2
3
4
5
6
7
| > cargo build
Compiling lists v0.1.0 (/Users/ADesires/dev/temp/lists)
error[E0507]: cannot move out of borrowed content
--> src/first.rs:35:29
|
35 | self.head = node.next;
| ^^^^^^^^^ cannot move out of borrowed content
|
扶额
拍桌
我们试图从 node 移出东西,而手里只有对它的共享引用。
应该退一步想我们要干什么。我们要:
- 检查链表是否为空。
- 若为空,直接返回
None - 若不为空
- 移除链表 head
- 取出它的
elem - 用它的
next 替换链表 head - 返回
Some(elem)
关键是我们要移除东西,也就是要按值拿到链表 head。通过 &self.head 得到的共享引用肯定做不到。我们也「只」有 self 的可变引用,所以移动东西的唯一办法是替换它。看来又要跳 Empty 之舞了!
试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
| pub fn pop(&mut self) -> Option<i32> {
let result;
match mem::replace(&mut self.head, Link::Empty) {
Link::Empty => {
result = None;
}
Link::More(node) => {
result = Some(node.elem);
self.head = node.next;
}
};
result
}
|
1
2
3
| cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
|
我的天
编译通过,没有任何警告!!!!
其实我要加一条个人 lint:我们建了 result 来返回,其实完全没必要!就像函数求值为最后一条表达式,每个块也求值为最后一条表达式。通常我们用分号压制这种行为,让块求值为空元组 ()。这就是像 push 这样不声明返回值的函数返回的东西。
所以可以写成:
1
2
3
4
5
6
7
8
9
| pub fn pop(&mut self) -> Option<i32> {
match mem::replace(&mut self.head, Link::Empty) {
Link::Empty => None,
Link::More(node) => {
self.head = node.next;
Some(node.elem)
}
}
}
|
更简洁、更地道。注意 Link::Empty 分支完全去掉了花括号,因为只有一条要求值的表达式。简单情况的 nice 简写。
1
2
3
| cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
|
不错,还能用!