Difference between borrow_mut on a RefCell<X> and RefCell<&X>
Asked Answered
F

1

6

If I get correctly it is not possible to create a mutable borrow over a std::rc::Rc in Rust, you have to use Cell or RefCell. But anyway I cannot understand how to use them. For example consider this simple example:

use std::cell::RefCell;

struct X (i32);

impl X {
    fn foo(&mut self) {
        self.0 = 0;
    }
}

fn main () {
    let x = X(5);
    let rcx = RefCell::new(&x);

    let mut mutx: std::cell::RefMut<&X> = rcx.borrow_mut();
    (*mutx).foo();
}

I get the following error:

16:5: 16:9 error: cannot borrow immutable local variable `mutx` as mutable
16     mutx.foo();

But if I remove the reference from line (and update type of mutx):

let rcx = RefCell::new(x);

Everything is fine. But I cannot understand why, since RefMut::deref_mut() -> &mut T the deference called at line 16 should return &&mut T in the first case, while &mut T in the second case. But since the compiler should apply many * as needed (If I get how deref coercion works) there should be no difference between RefMut<X>::deref_mut() and RefMut<&X>::deref_mut()

Edit: By mistake I forgot to write mut at line 15 as in the linked example is correctly is written. So now it's let mut mutx...

Fraley answered 15/9, 2015 at 16:51 Comment(0)
L
4

The problem stems from the fact that you've stored an immutable reference in the RefCell. I'm unclear why you would want such a thing. The normal pattern is to put the entire value into the RefCell, not just a reference:

fn main () {
    let rcx = RefCell::new(X(5));

    let mut mutx = rcx.borrow_mut();
    mutx.foo();
}

Problem from original question

You have two compounding errors. Let's check the entire error message:

<anon>:16:5: 16:12 error: cannot borrow immutable borrowed content as mutable
<anon>:16     (*mutx).foo();
              ^~~~~~~
<anon>:16:7: 16:11 error: cannot borrow immutable local variable `mutx` as mutable
<anon>:16     (*mutx).foo();
                ^~~~

Note the second error — "cannot borrow immutable local variable mutx". That's because you need to declare the mutx variable mutable:

let mut mutx: std::cell::RefMut<&X> = rcx.borrow_mut();

That will allow mutx to participate in DerefMut.

Laaland answered 15/9, 2015 at 17:10 Comment(5)
Sorry I put the mut in the linked code and I didn't update the code in the post. Anyway (regarding the first issue), also if I use RefCell::new(x) (without reference) I am storing a immutable value in RefCell, but this don't generate any error. The reason why I'm using a reference here is that my original code is actually a little more complicated (you can check here: is.gd/XoROad)Fraley
RefCell::new(x) [is] storing a immutable value — this is a common misconception, but it is not an immutable value. You might have an immutable binding to the value, but when you transfer ownership, you can pick what mutability you have.Laaland
@KillKRT Although Shepmaster is correct, it could be more clear. What you need to understand is that values have a fixed location (on the stack, heap or whatever). Those can be immutable. However, when you move from one value to another foo = bar, ownership of the contained memory is transferred. This transfer can be to a mutable location, which can then give transitive mutable access to the contents. But you can't do this with references because you can't move out of references, so you can never move the referenced data to a mutable location.Naif
@KillKRT RefCell is also a bit special, because it allows breaking the transitivity of mutability: an immutable RefCell can give you mutable access to its contents. If its contents is a value, it can give &mut T. If its contents, though, is a reference, it can only give you an &mut &T, which lets you modify where the inner reference points to but not the value pointed to by the inner reference.Naif
@Naif thank you for your clarification, it helped me a lot.Fraley

© 2022 - 2024 — McMap. All rights reserved.