Pop
和push一样,pop想要改变列表;除此之外,我们还想返回结果。然而pop还得处理一个特殊的边界情况:如果列表是空的呢?为了表示这个情况,我们使用可靠的Option类型:+
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进行模式匹配:
pub fn pop(&mut self) -> Option<i32> {
match self.head {
Link::Empty => {
// TODO
}
Link::More(node) => {
// TODO
}
};
}> 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(基本上也就是以可控的方式崩溃)。
无条件panic是一个发散函数(diverging function)的例子。发散函数永远不会返回到调用者,所以无论一个地方期待何种类型的返回值,它的返回值都能拿来用。在这里,unimplemented!被使用在期待一个Option<T>的地方。
注意到我们不需要在程序里写return。函数中的最后一个表达式也就隐式的成为它的返回值。这让我们可以更精炼的表达简单的逻辑。你也可以像C系语言一样,显式的return返回。
噢,Rust,别纠缠不休了!和往常一样,Rust对我们非常生气。值得感谢的是,这次它还给出了深入的信息! 默认情况下,模式匹配将尝试将其内容移动到新分支中,但是我们不能这样做,因为我们在这里不拥有自己的值。
Rust 表示我们应该在 match 添加一个引用来解决这个问题。🤷️让我们试试:
好耶,又编译了!现在让我们搞清楚实现逻辑。我们要创建一个Option,所以要为这个预留一个变量。在Empty情况下要返回 None,在More 情况下需要返回Some(i32),并且改变列表的head。来吧:
我们试图移出节点,但我们只有一个对它的共享引用。
我们也许应该退一步想想我们要做什么。我们希望:
检查列表是否为空。
如果是空的,返回None
如果是非空
移除list头部
移除该头部的
elem将列表的head替换为
next返回
Some(elem)
重要的一点事我们想要删除东西,这意味着我们需要按值获取list的head。我们肯定不能通过由ref node获取的共享引用来做这件事。我们也“只”拥有一个可变引用,所以能移动东西的唯一方法就是替换它。看来我们又在做Empty替换那一套了!
让我们试试这个:
我 的 天 哪
它编译了,一个警告都没有!!!!!
这里我要给出我的优化提示了:我们现在返回的是result变量的值,但实际上根本不用这么做!就像一个函数的结果是它的最后一个表达式,每个代码块的结果也是它的最后一个表达式。通常我们使用分号来阻止这一行为,这会让代码块的值变成空元组(tuple)()。这实际上也是不声明返回值的函数——例如push——返回的。
所以,我们可以把 pop 写成:
这是一个更简洁和习惯用法。请注意,Link::Empty分支完全丢失了它的大括号,因为我们只有一个表达式要计算。这是对于简单情况的简便处理。
不错,仍然能工作!
Last updated
Was this helpful?