Push

我们来实现将一个值推入列表的功能吧。push改变了列表,因此我们需要&mut self。还需要提供一个推入的i32参数:

  1. impl List {
  2. pub fn push(&mut self, elem: i32) {
  3. // TODO
  4. }
  5. }

首先,我们需要创建一个节点来存储我们的元素:

  1. pub fn push(&mut self, elem: i32) {
  2. let new_node = Node {
  3. elem: elem,
  4. next: ?????
  5. };
  6. }

next那里存储的是什么呢?呃,是整个原先的列表!我们…可以这么做么?

  1. impl List {
  2. pub fn push(&mut self, elem: i32) {
  3. let new_node = Node {
  4. elem: elem,
  5. next: self.head,
  6. };
  7. }
  8. }
  1. > cargo build
  2. Compiling lists v0.1.0 (file:///Users/ABeingessner/dev/lists)
  3. src/first.rs:19:10: 19:14 error: cannot move out of borrowed content
  4. src/first.rs:19 next: self.head,
  5. ^~~~
  6. error: aborting due to previous error
  7. Could not compile `lists`.

不不不。Rust在告诉我们正确的事,但是它的实际意义和修复它的方法并不明了:

cannot move out of borrowed content (无法移动出租借的内容)

我们尝试把self.head字段移动到next中,但是Rust不想让我们做这件事。这会导致在我们的租借结束,值返回到其所有者的时候,self只是部分初始化的。正如我们之前所说,这是你不能通过&mut做的唯一一件事:这样做是非常粗野的,而Rust则打算尽量保持礼貌(同时这也是极其危险的,但是并不是Rust关心它的原因)。

如果我们把东西放回去呢?具体的说,就是我们创建的节点:

  1. pub fn push(&mut self, elem: i32) {
  2. let new_node = Box::new(Node {
  3. elem: elem,
  4. next: self.head,
  5. });
  6. self.head = Link::More(new_node);
  7. }
  1. > cargo build
  2. Compiling lists v0.1.0 (file:///Users/ABeingessner/dev/lists)
  3. src/first.rs:19:10: 19:14 error: cannot move out of borrowed content
  4. src/first.rs:19 next: self.head,
  5. ^~~~
  6. error: aborting due to previous error
  7. Could not compile `lists`.

不行。在原则上,Rust是可以接受这样的行为的,但是它不会(出于数个原因——最重要的是安全性)我们需要某种方法得到head,而不让Rust发现它已经消失了。我们转而向声名狼藉的Rust黑客Indiana Jones寻求建议:

Indy准备进行mem::replace

噢没错,Indy建议采用mem::replace这一招。这个极其有用的函数让我们通过替换将一个值从租借中偷出来。让我们先在文件顶部把std::mem拉进来,这样mem就在局部作用域了:

  1. use std::mem;

然后恰当的使用它:

  1. pub fn push(&mut self, elem: i32) {
  2. let new_node = Box::new(Node {
  3. elem: elem,
  4. next: mem::replace(&mut self.head, Link::Empty),
  5. });
  6. self.head = Link::More(new_node);
  7. }

在将self.head替换为列表的新头部之前,我们临时的将它replace为Link::Empty。我不会撒谎:非要这么去做真是很糟糕。悲伤的是,我们必须这样(至少现在)。

不过,这样子push就完成了!或许吧。说真的,我们应该测试一下它。现在来说,最简单的测试方法是实现pop,然后确认它给出了正确的结果。