1.7 Drop
3 分钟阅读
原文链接: https://rust-unofficial.github.io/too-many-lists/first-drop.html
我们能造栈、压入、弹出,甚至还测过一切正常!
需要担心清理链表吗?技术上,完全不用!和 C++ 一样,Rust 用析构函数在资源用完时自动清理。若类型实现了名为 Drop 的 trait,它就有析构函数。Trait 是 Rust 对接口的 fancy 说法。Drop trait 的接口如下:
| |
基本上就是:「当你离开作用域时,我给你一点时间收拾善后」。
若你包含的类型已实现 Drop,而你要做的只是调用它们的析构函数,其实不必自己实现 Drop。对 List 来说,它只想 drop 自己的 head,head 又可能会尝试 drop 一个 Box<Node>。这些都会自动处理……但有一个坑。
自动处理会很糟。
考虑一条简单链表:
| |
当 list 被 drop 时,会尝试 drop A,A 会尝试 drop B,B 会尝试 drop C。有些人可能已经开始紧张了。这是递归代码,递归可能爆栈!
有些人会想:「这显然是尾递归,任何像样的语言都会保证这种代码不会爆栈」。事实上,这是错的!要明白为什么,我们试着写编译器必须做的事,像编译器那样手动为 List 实现 Drop:
| |
我们不能在释放内存之后再 drop Box 的内容,所以没法用尾递归方式 drop!只能手动为 List 写迭代式 drop,把节点从它们的 box 里 hoist 出来。
| |
| |
太好了!

过早优化的附加章节!
我们的 drop 实现其实和 while let Some(_) = self.pop() { } 非常像,后者当然更简单。有什么不同?一旦我们把链表泛化到存整数以外的东西,可能带来哪些性能问题?
点击展开答案
pop 返回 Option<i32>,而我们的实现只操作 Link(Box<Node>)。所以我们的实现只移动指向节点的指针,而基于 pop 的实现会移动节点里存的值。若有人用链表存 VeryBigThingWithADropImpl(VBTWADI)的实例,泛化后这可能非常昂贵。Box 能在原地运行其内容的 drop 实现,所以不受此问题影响。VBTWADI 恰恰是让人想用链表而不是数组的那类东西,在这种情况下表现差会有点失望。
若想两全其美,可以加一个新方法 fn pop_node(&mut self) -> Link,pop 和 drop 都能从中干净地推导出来。