Pop

push一样,pop想要改变列表;除此之外,我们还想返回结果。然而pop还得处理一个特殊的边界情况:如果列表是空的呢?为了表示这个情况,我们使用可靠的Option类型:+

pub fn pop(&mut self) -> Option<i32> {
    //TODO
}

Option<T>是一个表示一个值可能存在也可能不存在的enum。它要么是Some(T),要么是None。我们也可以像Link一样创建一个自己的enum,但是我们想让用户了解我们的返回类型到底是什么,而Option是如此的无处不在,每个人都知道它。实际上,因为它是如此的基本,它被隐式的导入到了每一个源文件的作用域中,也包括它的两个变体:SomeNone(这样我们就不用写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(基本上也就是以可控的方式崩溃)。

pub fn pop(&mut self) -> Option<i32> {
    match self.head {
        Link::Empty => {
            // TODO
        }
        Link::More(node) => {
            // TODO
        }
    };
    unimplemented!()
}

无条件panic是一个发散函数(diverging function)的例子。发散函数永远不会返回到调用者,所以无论一个地方期待何种类型的返回值,它的返回值都能拿来用。在这里,unimplemented!被使用在期待一个Option<T>的地方。

注意到我们不需要在程序里写return。函数中的最后一个表达式也就隐式的成为它的返回值。这让我们可以更精炼的表达简单的逻辑。你也可以像C系语言一样,显式的return返回。

> 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对我们非常生气。值得感谢的是,这次它还给出了深入的信息! 默认情况下,模式匹配将尝试将其内容移动到新分支中,但是我们不能这样做,因为我们在这里不拥有自己的值。

help: consider borrowing here: `&self.head`

Rust 表示我们应该在 match 添加一个引用来解决这个问题。🤷‍️让我们试试:

pub fn pop(&mut self) -> Option<i32> {
    match &self.head {
        Link::Empty => {
            // TODO
        }
        Link::More(ref node) => {
            // TODO
        }
    };
    unimplemented!()
}
> 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。来吧:

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
}
> 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

我们试图移出节点,但我们只有一个对它的共享引用。

我们也许应该退一步想想我们要做什么。我们希望:

  • 检查列表是否为空。

  • 如果是空的,返回None

  • 如果是非空

    • 移除list头部

    • 移除该头部的elem

    • 将列表的head替换为next

    • 返回Some(elem)

重要的一点事我们想要删除东西,这意味着我们需要按值获取list的head。我们肯定不能通过由ref node获取的共享引用来做这件事。我们也“只”拥有一个可变引用,所以能移动东西的唯一方法就是替换它。看来我们又在做Empty替换那一套了!

让我们试试这个:

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
}
cargo build

   Finished dev [unoptimized + debuginfo] target(s) in 0.22s

我 的 天 哪

它编译了,一个警告都没有!!!!!

这里我要给出我的优化提示了:我们现在返回的是result变量的值,但实际上根本不用这么做!就像一个函数的结果是它的最后一个表达式,每个代码块的结果也是它的最后一个表达式。通常我们使用分号来阻止这一行为,这会让代码块的值变成空元组(tuple)()。这实际上也是不声明返回值的函数——例如push——返回的。

所以,我们可以把 pop 写成:

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分支完全丢失了它的大括号,因为我们只有一个表达式要计算。这是对于简单情况的简便处理。

cargo build

   Finished dev [unoptimized + debuginfo] target(s) in 0.22s

不错,仍然能工作!

Last updated