1.4 入栈

原文链接: https://rust-unofficial.github.io/too-many-lists/first-push.html

我们来写把值压入链表的操作。push 会修改链表,所以需要 &mut self。还要接收一个要压入的 i32:

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

首先,需要创建一个节点来存放元素:

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

next 放什么?嗯,整个旧链表!我们能……就这么做吗?

1
2
3
4
5
6
7
8
impl List {
    pub fn push(&mut self, elem: i32) {
        let new_node = Node {
            elem: elem,
            next: self.head,
        };
    }
}
1
2
3
4
5
6
> cargo build
error[E0507]: cannot move out of borrowed content
  --> src/first.rs:19:19
   |
19 |             next: self.head,
   |                   ^^^^^^^^^ cannot move out of borrowed content

不行。Rust 说得对,但到底什么意思、该怎么办,确实不那么明显:

cannot move out of borrowed content

我们想把 self.head 字段移给 next,但 Rust 不让我们这么做。这样在借用结束、把 self「还回去」给合法主人时,self 就只被部分初始化了。正如前面说的,这是 &mut 唯一不能做的事:那太不礼貌了,Rust 很有礼貌(当然这也极其危险,但肯定不是它在乎的原因)。

要是放点东西回去呢?也就是我们正在创建的那个节点:

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

    self.head = Link::More(new_node);
}
1
2
3
4
5
6
> cargo build
error[E0507]: cannot move out of borrowed content
  --> src/first.rs:19:19
   |
19 |             next: self.head,
   |                   ^^^^^^^^^ cannot move out of borrowed content

没戏。原则上 Rust 其实可以接受这种写法,但它不会(原因很多——最严重的是异常安全)。我们需要某种方式在 Rust 察觉不到的情况下拿走 head。求教时,我们转向臭名昭著的 Rust 黑客印第安纳·琼斯:

Indy Prepares to mem::replace

啊对,Indy 建议 mem::replace 这一招。这个极其有用的函数让你通过替换为另一个值,从借用中偷走一个值。在文件顶部引入 std::mem,让 mem 在局部作用域里可用:

1
use std::mem;

然后恰当使用:

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

    self.head = Link::More(new_node);
}

这里我们先临时把 self.head 用 mem::replace 换成 Link::Empty,再换成链表的新 head。不瞒你说:不得不这么做挺遗憾的。可惜,目前只能这样(暂时)。

不过嘿,push 算写完了!大概吧。老实说应该测一下。现在最简单的办法大概是写 pop,并确认结果正确。

最后修改 August 23, 2026: 更新 (499855b16)