Search code examples
rustrcrefcell

Rc/RefCell with parent of same struct


I'm trying to convert some object oriented code into Rust. It was going okay until I ran into this situation.

struct A {
    root: Rc<RefCell<B>>,
}

struct B {
    parent: Weak<RefCell<B>>,
    c_lst: Vec<C>
    value: u32
}

struct C {
    parent: Weak<RefCell<B>>,
    b_lst: Vec<Rc<RefCell<B>>>,
    end: bool
}

I have three structs. I want to have a main struct (A here) hold a root of struct B. B and C need to access parent values as read only, but where the first Rc occurs they need to be mutable.

At start this function is called:

impl A {
    fn register(&mut self) {
        for c in self.root.borrow_mut().c_lst {
            c.parent = Rc::downgrade(&self.root);
        }
    }
}

Then I use an update function on A:

impl A {
    fn update(&mut self) {
        self.root.borrow_mut().update();
    }
}

impl B {
    fn update(&mut self) {
        for c in &mut self.c_lst {
            c.update();
        }
    }
}

impl C {
    fn update(&mut self) {
        match self.parent.upgrade() {
            Some(parent) => {
                // Fail because parent is already borrowed to call this update                       
                if parent.try_borrow().unwrap().value == 0 {
                    // Do stuff
                }
            },
            None => Panic!("Parent was freed")
        }
        if some_condition {
            self.spawn_b();
        }
        for b in &self.b_lst {
            b.borrow_mut().update();
        }
    }

    // This infinite loop in this state, but it's for the example
    fn spawn_b(&mut self) {
        let b = Rc::new(RefCell::new(B::new()));
        b.borrow_mut().parent = self.parent.clone();
        if !self.end {
            b.borrow_mut().c_lst = vec![C::new(Rc::downgrade(&b)), C::new(Rc::downgrade(&b))];
            for c in b.borrow_mut().c {
                c.spawn_b();
            }
        }
        self.b_lst.push(b);
    }
}

As you see in the code, I can't access my parent state. Any suggestions?


Solution

  • The Rc/Weak tree in your example is a straightforward way to implement trees with parent references in Rust. The only issue is that your update function requires mutable access to the node its updating, which locks out the lower nodes from accessing the parent.

    You're going to have to make an architectural change to solve this. A couple ways of doing it:

    • Split up the data into different RefCells or even Cells. Lock the main data while your own node is updating, then unlock it before recursing through the children, letting them have access.
    • Process the tree in two phases: one to gather the changes into a list, using only immutable borrows, then another to apply the stored changes, immutably and locally.
    • Move to an adjacency list representation, where the relations of each node are not stored in the node object itself but in a separate mapping object, that can be locked as needed.