我们是否需要为链表手动创建析构函数?

时间:2016-07-01 14:03:47

标签: rust

我正在阅读Learning Rust With Entirely Too Many Linked Lists并且我对链接列表(堆栈)needs a destructor的原因感到困惑。

我认为当列表值超出范围时,列表本身和所有节点都将被清理。它只是为了示范吗?

我尝试使用和不使用手动析构函数对版本进行基准测试,然后我找到了#34;没有析构函数"一个人有更好的表现:

for _ in 1..30000000 {
        let mut list = List::new();
        list.push(1);
        assert_eq!(list.pop(), Some(1));
}

使用手动析构函数:

real    0m11.216s
user    0m11.192s
sys     0m 0.020s

没有手动析构函数:

real    0m9.071s
user    0m9.044s
sys     0m0.004s

2 个答案:

答案 0 :(得分:6)

你是对的。该清单将清理自己。 正如作者所说:

  

所有这些都是自动处理的......只有一个故障。

然后他解释了为什么自动处理不好: 自动销毁过程为列表的头部调用drop,然后为第一个元素调用drop。等等等等。

这是一个函数调用一个函数调用一个函数(无限可能的重复),它会迟早炸掉你的堆栈。

例如,此测试会导致此类堆栈溢出:

#[test]
fn build_really_big_stack(){    
    let mut stack = List::new();
    for i in 0..1_000_000{
        stack.push(i);
    }
}

此外,如果您使用--release标记对可执行版本进行基准测试,则表明两个版本的执行几乎相同:

#[bench]
fn bench_auto_destructor(b: &mut Bencher) {
    b.iter(|| {
        {
            let mut list = List::new();
            for i in 0..1000 {                
                list.push(i);
            }
            assert_eq!(list.pop(), Some(999));
        }
    });    
}

#[bench]
fn bench_man_destructor(b: &mut Bencher){
b.iter(|| {
        {
            let mut list = ManualDestructorList::new();
            for i in 0..1000 {                
                list.push(i);
            }
            assert_eq!(list.pop(), Some(999));
        }
    });
}

test bench_auto_destructor ... bench:      81,296 ns/iter (+/- 302)
test bench_man_destructor  ... bench:      85,756 ns/iter (+/- 164)

只有一个元素,就像你的基准一样:

test bench_auto_destructor ... bench:          69 ns/iter (+/- 1)
test bench_man_destructor  ... bench:          67 ns/iter (+/- 2)

但阅读文章到最后。它的解释比我的好。

答案 1 :(得分:3)

作者让你为Link实现自己的drop是因为调用链表上的析构函数不是tail recursive,所以如果非常大C(即{{1}其节点数大于Rust编译器允许的堆栈帧数超出范围并因此被解除分配,然后当递归调用所有这些drop函数时,您将得到堆栈溢出错误。请阅读上面给出的链接以了解尾递归是什么,但将List函数替换为List的{​​{1}}函数,您就会明白作者为什么要写了你自己的析构函数。

想象一下recsum(),其中包含1_000_000 Link。当drop被取消分配时,您的堆栈将看起来像

List