Why does my iterative implementation of drop for a linked list still cause a stack overflow?

前端 未结 2 2060
暗喜
暗喜 2021-01-20 16:33

I am following Learning Rust With Entirely Too Many Linked Lists to write my first program in Rust. I changed the program to:

use std::mem;
         


        
相关标签:
2条回答
  • 2021-01-20 17:00

    Whenever you write recursive (or iterative) code, you need to have a stopping condition. Your code doesn't, so it loops forever.


    Producing a MCVE of your problem is always a good start:

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

    Then annotate the code to see where it's recurring:

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

    This prints out

    1
    2
    1
    2
    

    so delete everything after that:

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

    Why does this cause infinite recursion? You've defined it so that in order to drop List, you have to create a new List, which in turn needs to be dropped, which creates a new List, which...

    Add a stopping condition:

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

    No more infinite recursion.

    Then expand back out to the original and try again. It works for this test case, but not for List::More(Box::new(List::Nil)) so we shrink it back:

    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
    

    The problem now is that when we re-assign head, the value we are overwriting needs to be dropped, which triggers the recursion again.

    Fixing this is complicated. Like, surprisingly so. You ready for this?

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

    This now has two stopping conditions:

    1. Nil
    2. More(Nil)

    In every other case, we iteratively convert the More(x) into a More(Nil), which is handled by the stopping condition. That means that we only have a single depth of recursion: for each value that is dropped when the previous value of head goes out of scope when it is replaced.


    For your original code:

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

    In the original tutorial you linked, this isn't a problem because the definition of List::drop doesn't modify self at all so it's not self-recursive:

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-20 17:05

    You are getting a stack overflow because your drop function is infinitely recursive.

    The code below:

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

    Stores a List object in head, which will be dropped at the end of the scope. This means that, while you are dropping, you create a new list which also needs to be dropped. Repeat this enough times and you get a stack overflow.

    Note: in the tutorial you linked, they use let mut cur_link = mem::replace(&mut self.head, Link::Empty) to avoid recursion.

    0 讨论(0)
提交回复
热议问题