Immutable/mutable borrows on Rc<RefCell<SomeStruct>>

Hi all.

I was trying to make the code below to work:

fn issue() {
    trait Reader {
        fn read(&self, buf: &mut [u8]);
    }

    struct Abc {
        f: Box<dyn Reader>,
        buf: [u8; 32],
    }

    impl Reader for u8 {
        fn read(&self, buf: &mut [u8]) {
            buf[0] = 0;
        }
    }

    let buf = [0u8; 32];
    let abc = Rc::new(RefCell::new(Abc {
        f: Box::new(100u8),
        buf,
    }));
    let mut abc_mut_ref = abc.borrow_mut();
    // can't work, compiling error:
    // abc_mut_ref.f.read(&mut abc_ref.buf[..]);

    // can't do it like below, runtime error:
    // let abc_ref = abc.borrow();
    // abc_ref.f.read(&mut abc_mut_ref.buf[..]);
}

There are 2 solutions from the commented code, which are unfortunately both wrong.
And I found that if Rc/RefCell wasn't used (just use a mutable ref), it can work:

    // it can work like below:
    let abc2 = &mut Abc {
        f: Box::new(100u8),
        buf,
    };
    abc2.f.read(&mut abc2.buf[..]);

Is there any solution to this issue? Thanks.

Your first version is the right, it just needs some edge closing.

First. it should be:

abc_mut_ref.f.read(&mut abc_mut_ref.buf[..]);

And not:

abc_mut_ref.f.read(&mut abc_ref.buf[..]);

Second, you should define abc_mut_ref as following:

let abc_mut_ref = &mut *abc.borrow_mut();

Why is the &mut *?
RefCell::borrow_mut() returns a RefMut instance. This type implements DerefMut and Deref. Looking at their declaration:

pub trait Deref {
    type Target: ?Sized;
    pub fn deref(&self) -> &Self::Target;
}
pub trait DerefMut: Deref {
    pub fn deref_mut(&mut self) -> &mut Self::Target;
}

Now you can desugar your code:

let abc_mut_ref = abc.borrow_mut();
abc_mut_ref.f.read(&mut abc_mut_ref.buf[..]);
// Desugars into:
let mut abc_mut_ref: RefMut<Abc> = abc.borrow_mut();
let buf: &mut [u8; 32] = &mut DerefMut::deref_mut(&mut abc_mut_ref).buf[..];
let f: &Box<dyn Reader> = &Deref::deref(&abc_mut_ref).f;
Reader::read(f, buf);

See the problem? First we take a mutable reference to abc_mut_ref to call deref_mut(), then we take a shared reference to call deref() while the mutable reference is still alive! Usually, this works, because the compiler is smart enough to figure out that buf and f are disjoint fields and thus borrowing one can not affect the other. But here, since we hide the borrowed field behind DerefMut, the compiler does not knows that - from its point of view, RefMut::deref_mut() and RefMut::deref() can use any field!

Now let's desugar my solution:

let abc_mut_ref = &mut *abc.borrow_mut();
abc_mut_ref.f.read(&mut abc_mut_ref.buf[..]);
// Desugars into:
let abc_mut_ref: &mut Abc = DerefMut::deref_mut(&mut abc.borrow_mut());
let buf: &mut [u8; 32] = &mut abc_mut_ref.buf[..];
let f: &dyn Reader = &abc_mut_ref.f; // Actually has deref coercion
Reader::read(f, buf);

Now that we deref earlier, we get a normal reference, and the compiler can figure out disjoint fields.

1 Like