和push
一样,pop
想要改变列表;除此之外,我们还想返回结果。然而pop
还得处理一个特殊的边界情况:如果列表是空的呢?为了表示这个情况,我们使用可靠的Option
类型:+
Copy pub fn pop(&mut self) -> Option<i32> {
//TODO
}
Option<T>
是一个表示一个值可能存在也可能不存在的enum。它要么是Some(T)
,要么是None
。我们也可以像Link一样创建一个自己的enum,但是我们想让用户了解我们的返回类型到底是什么,而Option是如此的无处不在,每个人都知道它。实际上,因为它是如此的基本,它被隐式的导入到了每一个源文件的作用域中,也包括它的两个变体:Some
和None
(这样我们就不用写Option::None
)。
在Option<T>
尖括号里的部分指出Option实际上是一个泛型,它的泛型参数是T。这意味着你可以创建一个任何类型的Option!
所以,我们有这个Link
了,我们怎么检查它是Empty还是More呢?使用match
进行模式匹配:
Copy pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
}
Copy > 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(基本上也就是以可控的方式崩溃)。
Copy pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
unimplemented!()
}
注意到我们不需要在程序里写return
。函数中的最后一个表达式也就隐式的成为它的返回值。这让我们可以更精炼的表达简单的逻辑。你也可以像C系语言一样,显式的return
返回。
Copy > 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对我们非常生气。值得感谢的是,这次它还给出了深入的信息! 默认情况下,模式匹配将尝试将其内容移动到新分支中,但是我们不能这样做,因为我们在这里不拥有自己的值。
Copy help: consider borrowing here: `&self.head`
Rust 表示我们应该在 match 添加一个引用来解决这个问题。🤷️让我们试试:
Copy pub fn pop(&mut self) -> Option<i32> {
match &self.head {
Link::Empty => {
// TODO
}
Link::More(ref node) => {
// TODO
}
};
unimplemented!()
}
Copy > 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。来吧:
Copy pub fn pop(&mut self) -> Option<i32> {
let result;
match &self.head {
Link::Empty => {
result = None;
}
Link::More(ref node) => {
result = Some(node.elem);
self.head = node.next;
}
};
result
}
Copy > cargo build
Compiling lists v0.1.0 (/Users/ABeingessner/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
我们试图移出节点,但我们只有一个对它的共享引用。
我们也许应该退一步想想我们要做什么。我们希望:
重要的一点事我们想要删除 东西,这意味着我们需要按值 获取list的head。我们肯定不能通过由ref node
获取的共享引用来做这件事。我们也“只”拥有一个可变引用,所以能移动东西的唯一方法就是替换它 。看来我们又在做Empty替换那一套了!
让我们试试这个:
Copy 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
}
Copy cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
我 的 天 哪
它编译了,一个警告都没有!!!!!
这里我要给出我的优化提示了:我们现在返回的是result变量的值,但实际上根本不用这么做!就像一个函数的结果是它的最后一个表达式,每个代码块的结果也是它的最后一个表达式。通常我们使用分号来阻止这一行为,这会让代码块的值变成空元组(tuple)()
。这实际上也是不声明返回值的函数——例如push
——返回的。
所以,我们可以把 pop 写成:
Copy 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分支完全丢失了它的大括号,因为我们只有一个表达式要计算。这是对于简单情况的简便处理。
Copy cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.22s
不错,仍然能工作!