考虑这个实现:
pub enum List {
Empty,
Elem(i32, Box<List>),
}
2元素节点的内存布局是:
[] = Stack
() = Heap
[Elem A, ptr] -> (Elem B, ptr) -> (Empty *junk*)
考虑另一个链表实现:
struct Node {
elem: i32,
next: List,
}
pub enum List {
Empty,
More(Box<Node>),
}
2元素节点的内存布局是:
[ptr] -> (Elem A, ptr) -> (Elem B, ptr) -> (Empty, *junk*)
这两种内存布局非常相似,我并没有真正看到第二种实现方式比第一种更好,如claimed by Learning Rust With Entirely Too Many Linked Lists。
答案 0 :(得分:5)
由于此示例取自“Learning Rust With Entirely Too Many Linked Lists” book,我只引用the explanations from there:
[Elem A, ptr] -> (Elem B, ptr) -> (Empty *junk*)
有两个关键问题:
- 我们正在分配一个只是说“我实际上不是节点”的节点
- 我们的一个节点根本没有分配。
从表面上看,这两个似乎相互取消了。我们分配了一个额外的节点,但我们的一个节点根本不需要分配。但是,请考虑以下列表的潜在布局:
[ptr] -> (Elem A, ptr) -> (Elem B, *null*)
在这种布局中,我们现在无条件地堆分配节点。关键的区别在于我们的第一个布局中没有垃圾。
[...]
这里最重要的一点是,即使
Empty
只是一点信息,它也必然会为指针和元素消耗足够的空间,因为它必须准备好成为Elem
at随时。因此,第一个布局堆分配了一个充满垃圾的额外元素,比第二个布局消耗更多的空间。我们的一个节点根本没有被分配也许令人惊讶地比总是分配它更糟糕。这是因为它为我们提供了非统一节点布局。这对推送和弹出节点没有太大影响,但它确实对分割和合并列表有影响。
[...]
关于链表的一些好处是,您可以在节点本身中构建元素,然后在列表中自由地将其随机移动,而无需移动它。你只是摆弄指针,东西被“移动”。布局1会破坏此属性。
这只是其中的一些关键点。我实际上认为这本书的作者给出的解释在提供正确的推理和从第一次迭代到下一次迭代所涉及的思考过程中确实是优秀的。
我建议你重读整章,确保你理解那里的要点。如果您对个别陈述有明确的疑问,可以专门询问。
答案 1 :(得分:3)
第二种布局实际上是
[More(ptr)] -> (Elem A, More(ptr)) -> (Elem B, Empty)
或
[Elem A, More(ptr)] -> (Elem B, Empty)
空元素没有堆分配。