3

With a simple object, I can get mutable references to individual fields:

struct MyObject {
    pub a: i32,
    pub b: i32,
}

fn func_1(obj: &mut MyObject) {
    let a = &mut obj.a;
    let b = &mut obj.b;

    *a += 1;
    *b *= 2;
}

This doesn't work if obj is a MutexGuard or RefMut:

fn func_3(mtx: &Mutex<MyObject>) {
    let mut obj = mtx.lock().unwrap();
    let a = &mut obj.a;
    let b = &mut obj.b; // fails
    ...
}

fn func_4(rfc: &mut RefCell<MyObject>) {
    let mut obj = rfc.borrow_mut();
    let a = &mut obj.a;
    let b = &mut obj.b; // fails
    ...
}

Both fail with:

error[E0499]: cannot borrow `obj` as mutable more than once at a time
  --> src/main.rs:28:18
   |
27 |     let a = &mut obj.a;
   |                  --- first mutable borrow occurs here
28 |     let b = &mut obj.b; // fails
   |                  ^^^ second mutable borrow occurs here

However, this does work if obj is a Box:

fn func_2(obj: &mut Box<MyObject>) {
    let a = &mut obj.a;
    let b = &mut obj.b;

    *a += 1;
    *b *= 2;
}

See it on the Rust Playground.

My main question is why. Why does the compiler know that this is okay for Box, but not for the others? Is Box special?

kmdreko
  • 42,554
  • 6
  • 57
  • 106

1 Answers1

4

Yes, this is one of the ways in which Box is still special, despite rather a lot of effort having been put into making it appear like any other type.

RefMut is implemented in plain Rust. The reason you can take references to a member like &mut obj.a is because RefMut implements Deref and DerefMut, and the compiler uses DerefMut to take an &mut RefMut<'_, MyObject> and turn it into a &mut MyObject to access the field. But the compiler doesn't know that the borrow of obj.a is disjoint from the borrow of obj.b because the implementation of DerefMut is opaque.

Box is different because dereferencing a Box<T> does not go through Deref or DerefMut (except in a generic context). Instead, the compiler has special code that knows what a Box<T> is and how to dereference it to get a T. This is how the compiler knows that obj.a and obj.b are disjoint for Box<T>, but not for RefMut<'_, T> or MutexGuard<'_, T>.

Box is the only type that is special in this particular way.

Other links about the specialness of Box

trent
  • 25,033
  • 7
  • 51
  • 90
  • So, for consistency's sake, should I avoid using this "feature" and just treat it like a normal `Deref`? – kmdreko Feb 17 '20 at 02:19
  • 2
    @kmdreko If you obtain a `&mut T` out of an `&mut impl Deref` by using `&mut*myDerefValue`, then the regular rules will apply once more. – Optimistic Peach Feb 17 '20 at 02:25