1
votes

Being new to rust I wanted to play with some data structures and ended up with something like a node type without payload.

use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Drop;

#[derive(Debug)]
struct Container<'a> {
    next : Option<&'a RefCell<Container<'a>>>,
}

impl<'a> Container<'a> {
    fn new() -> Container<'a> {
        Container { next: None }
    }

    fn set(&mut self, next: &'a RefCell<Container<'a>>) {
        self.next = Some(next);
    }
}

The goal was to have these nodes not own their neighbours, so std::rc::Rc was out of the question. So I did some testing which went fine:

fn main() {
    // items:
    let cont_1 = RefCell::new(Container::new());
    let cont_2 = RefCell::new(Container::new());

    let b_1 = &cont_1;
    let b_2 = &cont_2;

    (*b_2).borrow_mut().set(b_1);
    (*b_1).borrow_mut().set(b_2);

    println!("{:?}", b_1.borrow());
}

Since I was playing around I then tried to implement the Drop trait on the Container type

impl<'a> Drop for Container<'a>{
    fn drop(&mut self) {}
}

which results in two of (the other one for cont_2)

error[E0597]: `cont_1` does not live long enough
  --> src/main.rs:11:15
   |
11 |     let b_1 = &cont_1;
   |               ^^^^^^^ borrowed value does not live long enough
...
18 | }
   | -
   | |
   | `cont_1` dropped here while still borrowed
   | borrow might be used here, when `cont_1` is dropped and runs the destructor for type `std::cell::RefCell<Container<'_>>`

Now, I believe, that Drop causes the deallocation to be at the end of scopes otherwise it would usually take place after the last use? But either way the complaint is about the value not living long enough... I have tried adding drop(...) in between, but failed. I guess I dont even understand how exactly the order of deallocation changed. I would expect that cont_1 would be deallocated last since it was initialized/declared first meaning that I don't really understand how it could still be borrowed.

1
"The goal was to have these nodes not own their neighbours, so std::rc::Rc was out of the question." ???Stargateur
"So I did some testing which went fine" define "fine" because running your code blow the stack.Stargateur
regarding std::rc::Rc : my understanding is, that it causes the pointer to be owned by multiple entities by calling .clone(). regarding "fine" : yes, admittedly that was very loose. "went fine" may be substituted with "compiled".Jonas

1 Answers

1
votes

What would happen if in your drop() implementation you use self.next.unwrap()...? Since one of your variables will necessarily be dropped before the other, the last one will have a dangling reference, and so undefined behavior. So you code is correct in not to compile.

IMO, the solution is to use some kind of reference counted pointers. If you do not want Rc, because they do not own the neighbors (it will create a reference loop and thus leak your objects), you can use Weak references. Something like this (playground):

use std::cell::RefCell;
use std::ops::Drop;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Container {
    next : Option<Weak<RefCell<Container>>>,
}

impl Container {
    fn new() -> Container {
        Container { next: None }
    }
    fn set(&mut self, next: &Rc<RefCell<Container>>) {
        self.next = Some(Rc::downgrade(next));
    }
}

impl Drop for Container{
    fn drop(&mut self) {}
}

fn main() {
    // items:
    let cont_1 = Rc::new(RefCell::new(Container::new()));
    let cont_2 = Rc::new(RefCell::new(Container::new()));

    cont_1.borrow_mut().set(&cont_1);
    cont_2.borrow_mut().set(&cont_2);

    println!("{:?}", cont_1.borrow());
}