作为Rust的学习项目,我有一个非常简单(工作,如果不完整)的单链表实现。结构的声明如下:
type NodePtr<T> = Option<Box<Node<T>>>;
struct Node<T> {
data: T,
next: NodePtr<T>,
}
pub struct LinkedList<T> {
head: NodePtr<T>,
}
实施size
和push_front
都是相当直截了当的,尽管迭代地执行大小确实涉及一些“与借阅检查员打架。”
我想要尝试的下一件事是向tail
结构添加LinkedList
指针。启用有效的push_back
操作。在这里,我遇到了一堵墙。起初我尝试使用Option<&Box<Node<T>>>
然后Option<&Node<T>>
。这两个导致在任何地方都散布'a
,但最终仍然无法承诺tail
有效的终生检查。
我已经得出了一个初步结论:这些定义是不可能的:没有办法保证编译器tail
在我所在的地方有效认为它是有效的。我可以实现这一目标的唯一方法是让我的所有指针都是Rc<_>
或Rc<RefCell<_>>
,因为这些是指向同一个对象(最终节点)的两个指针的唯一安全方法。 / p>
我的问题:这是正确的结论吗?更一般地说:对于数据结构中的无主指针,什么是惯用的Rust解决方案?在我看来,引用计数对于如此简单的事情看起来非常重,所以我认为我必须遗漏一些东西。 (或者我可能还没有考虑到对于记忆安全的正确思维方式。)
答案 0 :(得分:8)
是的,如果你想用尾指针编写一个单链表,你有三个选择:
Option<Rc<RefCell<Node<T>>>>
Option<Rc<Node<T>>>
tail: *mut Node<T>
*mut
会更有效率,并不像Rc
实际上会阻止你产生完全无意义的状态(如你正确地推断出)。它只是保证它们不会导致段错误(而使用RefCell它可能仍会导致运行时崩溃......)。
最终,任何比香草单链接更复杂的链表都有一个所有权故事太复杂,无法安全有效地编码Rust的所有权系统(它不是一棵树)。我个人赞成在这一点上接受不安全的事情并依靠单元测试来完成一个终点线(为什么写一个次优的数据结构......?)。