Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I mutably borrow separate fields from a Box, but not other ref types?

Tags:

rust

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?

like image 476
kmdreko Avatar asked Oct 12 '25 13:10

kmdreko


1 Answers

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

  • Rust Tidbits: Box Is Special
  • Why do I get "cannot move out of `item` because it is borrowed" for a custom type but not a Box?
  • Why can I not borrow a variable as mutable more than once at a time with a &mut Box<T> while &mut T works?
like image 132
trent Avatar answered Oct 15 '25 19:10

trent