为什么我对链表的drop的迭代实现仍会导致堆栈溢出?

时间:2017-11-01 09:38:04

标签: list rust stack-overflow

我正在跟随Learning Rust With Entirely Too Many Linked Lists在Rust写下我的第一个程序。我将程序更改为

use std::mem;

#[derive(Debug)]
pub enum List {
    Nil,
    More(Box<Node>),
}

#[derive(Debug)]
pub struct Node {
    val: i32,
    next: List
}

impl List {
    pub fn new() -> Self {
        List::Nil
    }

    pub fn insert(&mut self, v : i32) {
        let old_head = mem::replace(&mut *self, List::Nil);
        let new_head = List::More(Box::new(Node { val : v, next: old_head}));
        *self = new_head
    }

    pub fn remove(&mut self) -> Option<i32> {
        match mem::replace(&mut *self, List::Nil) {
            List::Nil => {
                None
            },
            List::More(ref mut boxed_node) => {
                let result = Some(boxed_node.val);
                *self = mem::replace(&mut boxed_node.next, List::Nil);
                result
            }
        }
    }
}

impl Drop for List {
    fn drop(&mut self) {
        let mut head = mem::replace(&mut *self, List::Nil);

        while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
            head = mem::replace(&mut node.next, List::Nil);
        }
    }
}

#[cfg(test)]
mod test {
    use super::List;

    #[test]
    fn basics() {
        let mut list = List::new();
        list.insert(7);
        assert_eq!(Some(7), list.remove());
        assert_eq!(None, list.remove());

        list.insert(1);
        list.insert(2);
        list.insert(3);

        assert_eq!(Some(3), list.remove());
        assert_eq!(Some(2), list.remove());
        assert_eq!(Some(1), list.remove());
        assert_eq!(None, list.remove());
    }

    #[test]
    fn drop_long_list() {
        let mut list = List::new();
        for i in 1..100000 {
            list.insert(i);
        }
    }
}

我的两个测试都在drop中出现堆栈溢出失败。是因为RHS中的*self吗?

我不知道let mut head = mem::replace(&mut *self, List::Nil);发生了什么。

我的理解是:

  1. 它在List::Nil中设置了self
  2. self
  3. 中放置head的原始值

    *self还在做些什么吗?

    我也试过这个版本的drop

    impl Drop for List {
        fn drop(&mut self) {
            let mut head = mem::replace(self, List::Nil);
    
            while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
                head = mem::replace(&mut node.next, List::Nil);
            }
        }
    }
    

    另一次尝试:

    impl Drop for List {
        fn drop(&mut self) {
            while true {
                match self {
                    &mut List::Nil => break,
                    &mut List::More(ref mut node) => {
                        *self = mem::replace(&mut node.next, List::Nil)
                    }
                }
            }
        }
    }
    
    error[E0506]: cannot assign to `*self` because it is borrowed
      --> src\list.rs:48:21
       |
    47 |                 &mut List::More(ref mut node) => {
       |                                 ------------ borrow of `*self` occurs here
    48 |                     *self = mem::replace(&mut node.next, List::Nil)
       |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `*self` occurs here
    

2 个答案:

答案 0 :(得分:2)

您正在获得堆栈溢出,因为您的drop函数是无限递归的。

以下代码:

Class<T>

let mut head = mem::replace(self, List::Nil); 中存储List对象,该对象将在范围的末尾删除。这意味着,在您放弃时,您将创建一个也需要删除的新列表。重复这么多次就会出现堆栈溢出。

注意:在您关联的教程中,他们使用head来避免递归。

答案 1 :(得分:2)

每当你编写递归(或迭代)代码时,你需要有一个停止条件。你的代码没有,所以它永远循环。

产生MCVE问题总是一个好的开始:

use std::mem;

#[derive(Debug)]
pub enum List {
    Nil,
    More(Box<List>),
}

impl Drop for List {
    fn drop(&mut self) {
        let mut head = mem::replace(self, List::Nil);

        while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
            head = mem::replace(node, List::Nil);
        }
    }
}

#[test]
fn basics() {
    List::Nil;
}

然后注释代码以查看它重复出现的位置:

fn drop(&mut self) {
    eprintln!("1");
    let mut head = mem::replace(self, List::Nil);
    eprintln!("2");
    while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
        eprintln!("3");
        head = mem::replace(node, List::Nil);
        eprintln!("4");
    }
    eprintln!("5");
}

打印出来

1
2
1
2

所以在那之后删除所有内容:

fn drop(&mut self) {
    eprintln!("1");
    let mut head = mem::replace(self, List::Nil);
    eprintln!("2");
}

为什么会导致无限递归?您已对其进行了定义,以便放弃List,您必须创建一个新的List,然后需要将其删除,这会创建一个新的List,其中...

添加停止条件:

fn drop(&mut self) {
    if let List::Nil = *self { return }

    let mut head = mem::replace(self, List::Nil);

    while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
        head = mem::replace(node, List::Nil);
    }
}

不再有无限的递归。

然后展开回原版并再试一次。它适用于此测试用例,但不适用于List::More(Box::new(List::Nil)),因此我们将其缩小:

fn drop(&mut self) {
    eprintln!("1");
    if let List::Nil = *self { return }
    eprintln!("2");
    let mut head = mem::replace(&mut *self, List::Nil);
    eprintln!("3");
    while let List::More(ref mut node) = mem::replace(&mut head, List::Nil) {
        eprintln!("4");
        head = mem::replace(node, List::Nil);
        eprintln!("5");
    }
    eprintln!("6");
}
1
2
3
4
1
5
1
2
3
4
1
5

现在的问题是,当我们重新分配head时,需要删除我们覆盖的值,这会再次触发递归。

修复此问题复杂。就像,令人惊讶的那样。你准备好了吗?

impl Drop for List {
    fn drop(&mut self) {
        match *self {
            List::Nil => return,
            List::More(ref more) => {
                if let List::Nil = **more {
                    return;
                }
            }
        }

        let mut head = mem::replace(self, List::Nil);

        while let List::More(ref mut next) = {head} {
            head = mem::replace(next, List::Nil);
        }
    }
}

现在有两个停止条件:

  1. Nil
  2. More(Nil)
  3. 在所有其他情况下,我们迭代地将More(x)转换为More(Nil),这由停止条件处理。这意味着我们只有一个递归深度:为每个值在替换时head的前一个值超出范围时被删除。

    原始代码:

    impl Drop for List {
        fn drop(&mut self) {
            match *self {
                List::Nil => return,
                List::More(ref more) => {
                    if let List::Nil = more.next {
                        return;
                    }
                }
            }
    
            let mut head = mem::replace(self, List::Nil);
    
            while let List::More(ref mut node) = {head} {
                head = mem::replace(&mut node.next, List::Nil);
            }
        }
    }
    

    在您链接的原始教程中,这不是问题,因为List::drop的定义根本不会修改self,所以它不是自我递归的:

    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);
            }
        }
    }