这两种单向链表的删除实现方式有什么不同?

8
我正在学习《用过多的链表学习 Rust》这本书,并且对该书中初始链表的 drop 实现有疑问。该链表由一个堆栈分配的 link 组成,该 link 可能为空,也可能指向一个带有一些关联数据的堆分配的 Node。
struct List {
    head: Link,
}

enum Link {
    Empty,
    More(Box<Node>),
}

struct Node {
    data: i32,
    next: Link,
}

这本书中给出的drop trait实现使用std::mem::replace来获取列表中节点的所有权。
// Copied from the book, omitting comments
impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = mem::replace(&mut self.head, Link::Empty);
        while let Link::More(mut boxed_node) = cur_link {
            cur_link = mem::replace(&mut boxed_node.next, Link::Empty);
        }
    }
}

我很困惑为什么这本书使用mem::replace来获取节点的下一个指针的所有权。似乎我们已经从while let行拥有了boxed_node的所有权,我们可以直接将cur_link设置为node.next而不是使用mem::replace
我编写了这个替代实现,它可以成功编译。我想知道两种drop方法之间是否有任何区别。
impl Drop for List {
    fn drop(&mut self) {
        let mut link = mem::replace(&mut self.head, Link::Empty);
        while let Link::More(node) = link {
            link = node.next;
        }
    }
}

2
看起来你是对的。我的猜测是早期版本做了 impl Drop for Node,这可以在原始代码中工作,但不能在你的更改中使用(playground)。然而,在 githubhistory 中似乎一直是这样的。 - rodrigo
1
值得注意的是,@rodrigo提到的同样问题也适用于将类型变成通用类型 - 因此,如果书中的代码版本最初是基于通用版本的,并且只是专门针对i32进行了优化,那么我不会感到惊讶。 - apetranzilla
值得注意的是,如果“Link”具有“Drop”实现,则两个版本都无法正常工作。 - Peter Hall
@apetranzilla 即使它是通用的,在两个版本中都可以正常工作。(请参见playground - Peter Hall
1个回答

2

是的,你说得没错。那个教程中实现Drop的整个重点就在于避免递归调用,而你的实现使用更少的代码(并且最后生成的汇编代码也更少)也实现了同样的效果。

话虽如此,教程中的实现更加明确,可以认为更容易看出发生了什么。mem::replace确保将Link替换为Empty,清晰地表明所有的盒子都被删除了。这可能是教程作者有意为之,或者是一个疏忽。

附加部分也许可以给出作者的想法线索。他们简化Drop实现的想法是添加一个方法,该方法对于简化其他方法也很有用,因此仍需要维护结构的完整性。他们可能在看到更大的图像时忽略了在Drop实现中不需要这样做的事实,否则他们可能特别希望该实现以便附加部分有意义。

你的版本可能更有效率,但可能需要第二次查看才能真正理解它在做什么。不过我还是会选择你的版本,因为它更短并且执行的工作更少。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接