Search code examples
rustmutablereference-countingborrowingrefcell

How to create a value with reference-counted references to itself while checking an already-borrowed field?


I'm trying to create a mutable structure B which stores instances of other structure A that hold references to B. I want an implementation such that any mutation done to the original B propagates to the references held in the As.

However, during the mutation, I have to check a field from the B instance wrapped in A instance, thus breaking the "one mutable xor many immutable" rule. I need to borrow immutably while borrowed mutably, but the immutable borrow is internal to the mutating function and does not outlive its scope.

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

#[derive(Debug)]
struct A {
    t: Weak<RefCell<B>>,
}

#[derive(Debug)]
struct B {
    a: usize,
    item: Option<A>,
}

impl B {
    pub fn mutate(&mut self, item: A) {
        {
            let t = item.t.upgrade().unwrap();

            // This check has to be done.
            //
            assert![t.borrow().a == self.a, "not equal"];
            //      ~~~~~~~~~~ panics on this borrow
        }
        //
        // The immutable borrow should end here.

        self.item = Some(item);
        self.a += 1;
    }
}

fn main() {
    let b = B { item: None, a: 0 };

    let bc = Rc::new(RefCell::new(b));

    let f = A {
        t: Rc::downgrade(&bc),
    };

    bc.borrow_mut().mutate(f);

    println!["{:?}", bc.borrow().item.as_ref().unwrap().t.upgrade()];
}

playground

It panics with:

thread 'main' panicked at 'already mutably borrowed: BorrowError'

Can these requirements be met using only Rc<RefCell<B>>? If not, do I have to sink into unsafe code?


Solution

  • The docs for RefCell::borrow say:

    Panics

    Panics if the value is currently mutably borrowed. For a non-panicking variant, use try_borrow.

    Using try_borrow with the knowledge that any existing borrow must be self (and thus is equal) allows your code to compile:

    let eq = t.try_borrow().map_or(true, |v| v.a == self.a);
    assert![eq, "not equal"];
    

    See also: