Question on Mutable Borrow when use RefCell

Today, while learning Rust, I was troubled by the following question.
Since we can obtain a mutable borrow through borrow_mut , why is it necessary to explicitly declare y as mut ? (For variable yy , it is not necessary.)

fn main() {
    let x = RefCell::new(1);
    let y = x.borrow_mut();
    *y += 1;

    let mut xx = 1;
    let yy = &mut xx;
    *yy += 1;
}

Error:

error[E0596]: cannot borrow `y` as mutable, as it is not declared as mutable
 --> src\main.rs:7:6
  |
7 |     *y += 1;
  |      ^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
6 |     let mut y = x.borrow_mut();
  |         +++

The type returned by RefCell::borrow_mut() is not a plain mutable reference. If you think about it, it couldn't be a plain mutable reference. The whole point of RefCell is that it can keep track of the outstanding borrows, so it has to return a guard type that signals back to the originating RefCell when it goes out of scope.

That type is Ref (for borrow()) and RefMut (for borrow_mut()). These types give access to the underlying value via their Deref and DerefMut impls. This means that the RefMut guard itself has to be declared as mutable, because the signature of DerefMut::deref_mut() is (&mut Self) -> &mut Self::Target, so you have to be able to take a mutable reference to the guard in order for it to return a mutable reference to the inner value.

5 Likes

To complete the picture: this isn't the case for &mut because the compiler treats mutable references specially. It knows how to dereference a &mut i32 without going through the Deref implementation, so it's not necessary to take a &mut &mut i32 to get there in the same way that it's necessary to take a &mut RefMut<i32> to dereference the guard when using RefCell.

5 Likes

That difference can matter in other ways too. Just a day ago I was reminded that you have to reborrow in order to exclusively borrow non-overlapping parts of a RefMut. In the following program, takes_a_refcell doesn't compile but the other two variants do:

use std::cell::RefCell;

struct Things {
    thing_1: String,
    thing_2: i32,
}

fn takes_two_refs(_: &mut String, _: &mut i32) {}

/////////////////////////////////////////////////////////

fn takes_a_mut_ref(x: &mut Things) {
    takes_two_refs(&mut x.thing_1, &mut x.thing_2);
}

fn takes_a_refcell(cell: &RefCell<Things>) {
    let mut x = cell.borrow_mut();
    takes_two_refs(&mut x.thing_1, &mut x.thing_2); // error: cannot borrow `x` as mutable more than once at a time
}

fn takes_a_refcell_and_derefs(cell: &RefCell<Things>) {
    let mut x = cell.borrow_mut();
    let x = &mut *x; // reborrow RefMut as &mut
    takes_two_refs(&mut x.thing_1, &mut x.thing_2);
}

(If you find yourself actually in this situation, note that you don't need to write the second let; thanks to temporary lifetime extension, let x = &mut *cell.borrow_mut(); will do.)

You can also do projection (somewhat tediously) with map_split.

fn takes_a_refcell_and_maps(cell: &RefCell<Things>) {
    let (mut thing_1, mut thing_2) = RefMut::map_split(
        cell.borrow_mut(),
        |x| (&mut x.thing_1, &mut x.thing_2)
    );
    takes_two_refs(&mut thing_1, &mut thing_2);
}
2 Likes

This does however have some overhead of increasing (and decreasing when dropped) the counter for mutable borrows in the RefCell, so I'd only use it to call some API that deliberately wants to be passed the RefMut itself, not just a mutable reference to its target.

1 Like

I agree.

Alright, I understand most of what you said, but I still have a question:

You can directly execute a statement like

*x.borrow_mut() += 1

However, in the function signature of borrow_mut(), which is pub fn borrow_mut(&self) -> RefMut<'_, T>, the return value is not declared as mut. So, why does *x.borrow_mut() += 1 work?

Here comes the doubt.
——" This means that the RefMut guard itself has to be declared as mutable."

The places of return values are determined by the call site and can be mutable places. (It would be pretty awful if the declaration limited the call sites.)

https://doc.rust-lang.org/stable/reference/expressions.html#mutability

(mut bindings, unlike &mut, is basically a lint anyway.)

Temporaries are always mutable.

1 Like

Bad news: Understanding these problems is more of a headache than solving them. :face_with_spiral_eyes:

Good news: I found a helpful article.Borrow multiple fields from struct :star_struck:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.