3.1 数据布局

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

好,重新考虑布局。

持久化链表最重要的是,你可以几乎免费地操纵链表尾部:

例如,持久化链表上常见的工作负载是这样的:

1
2
3
list1 = A -> B -> C -> D
list2 = tail(list1) = B -> C -> D
list3 = push(list2, X) = X -> B -> C -> D

但最后我们希望内存看起来像这样:

1
2
3
4
5
6
7
list1 -> A ---+
              |
              v
list2 ------> B -> C -> D
              ^
              |
list3 -> X ---+

用 Box 根本做不到,因为 B 的所有权是共享的。该由谁释放?如果我 drop list2,会释放 B 吗?用 Box 我们当然会这么期望!

函数式语言——事实上几乎所有其他语言——靠垃圾回收蒙混过关。有了 GC 的魔法,只有没人再看 B 时才会释放。好耶!

Rust 没有这些语言那种垃圾回收器。它们有追踪式 GC,会在运行时翻遍所有内存自动找出垃圾。Rust 目前只有引用计数。引用计数可以看作非常简单的 GC。对很多工作负载,吞吐量明显低于追踪式收集器,而且一旦形成环就会彻底崩溃。但嘿,我们只有这个!好在我们的用例永远不会遇到环(欢迎自己证明——我肯定不会)。

那怎么做引用计数垃圾回收?Rc!Rc 就像 Box,但可以复制,而且只有从它派生的所有 Rc 都 drop 后内存才会释放。可惜这种灵活性代价很高:我们只能对其内部取共享引用。这意味着我们没法真正从链表里取出数据,也没法修改它们。

那布局长什么样?之前是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
pub struct List<T> {
    head: Link<T>,
}

type Link<T> = Option<Box<Node<T>>>;

struct Node<T> {
    elem: T,
    next: Link<T>,
}

能把 Box 改成 Rc 吗?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 在 third.rs 中

pub struct List<T> {
    head: Link<T>,
}

type Link<T> = Option<Rc<Node<T>>>;

struct Node<T> {
    elem: T,
    next: Link<T>,
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cargo build

error[E0412]: cannot find type `Rc` in this scope
 --> src/third.rs:5:23
  |
5 | type Link<T> = Option<Rc<Node<T>>>;
  |                       ^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
1 | use std::rc::Rc;
  |

哦糟,真损。不像可变链表用的那些东西,Rc 逊到连每个 Rust 程序默认导入都做不到。真逊。

1
use std::rc::Rc;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
cargo build

warning: field is never used: `head`
 --> src/third.rs:4:5
  |
4 |     head: Link<T>,
  |     ^^^^^^^^^^^^^
  |
  = note: #[warn(dead_code)] on by default

warning: field is never used: `elem`
  --> src/third.rs:10:5
   |
10 |     elem: T,
   |     ^^^^^^^

warning: field is never used: `next`
  --> src/third.rs:11:5
   |
11 |     next: Link<T>,
   |     ^^^^^^^^^^^^^

看起来靠谱。Rust 写起来依然完全轻松。我打赌全局查找替换 Box 为 Rc 就完事了!

……

不行。真不行。

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