使用尾指针编写链表的惯用方法是什么?

时间:2015-06-15 04:20:21

标签: linked-list rust reference-counting

作为Rust的学习项目,我有一个非常简单(工作,如果不完整)的单链表实现。结构的声明如下:

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

struct Node<T> {
    data: T,
    next: NodePtr<T>,
}

pub struct LinkedList<T> {
    head: NodePtr<T>,
}

实施sizepush_front都是相当直截了当的,尽管迭代地执行大小确实涉及一些“与借阅检查员打架。”

我想要尝试的下一件事是向tail结构添加LinkedList指针。启用有效的push_back操作。在这里,我遇到了一堵墙。起初我尝试使用Option<&Box<Node<T>>>然后Option<&Node<T>>。这两个导致在任何地方都散布'a,但最终仍然无法承诺tail有效的终生检查。

我已经得出了一个初步结论:这些定义是不可能的:没有办法保证编译器tail在我所在的地方有效认为它是有效的。我可以实现这一目标的唯一方法是让我的所有指针都是Rc<_>Rc<RefCell<_>>,因为这些是指向同一个对象(最终节点)的两个指针的唯一安全方法。 / p>

我的问题:这是正确的结论吗?更一般地说:对于数据结构中的无主指针,什么是惯用的Rust解决方案?在我看来,引用计数对于如此简单的事情看起来非常重,所以我认为我必须遗漏一些东西。 (或者我可能还没有考虑到对于记忆安全的正确思维方式。)

1 个答案:

答案 0 :(得分:8)

是的,如果你想用尾指针编写一个单链表,你有三个选择:

  • 安全可变:使用NodePtr = Option<Rc<RefCell<Node<T>>>>
  • 安全且不可变:使用NodePtr = Option<Rc<Node<T>>>
  • 不安全和可变:使用tail: *mut Node<T>

*mut会更有效率,并不像Rc实际上会阻止你产生完全无意义的状态(如你正确地推断出)。它只是保证它们不会导致段错误(而使用RefCell它可能仍会导致运行时崩溃......)。

最终,任何比香草单链接更复杂的链表都有一个所有权故事太复杂,无法安全有效地编码Rust的所有权系统(它不是一棵树)。我个人赞成在这一点上接受不安全的事情并依靠单元测试来完成一个终点线(为什么写一个次优的数据结构......?)。