Unexpected "borrow as mutable more than once"

I found that when I wrap a struct with ManuallyDrop, I can not borrow two fields from this struct as mutable at the same time. When I remove the ManuallyDrop, every thing goes well. Also, The error does not happen If I use a Box to wrap the struct instead of ManuallyDrop.

What's the reason behind this? Is there something special with ManuallyDrop?

My code is attached below. Thank you for your answer in advance.

use std::mem::ManuallyDrop;

fn main() {
    struct A {
        a: i32, 
        b: i32,
    }
    
    let mut a = ManuallyDrop::new(A{a:0, b:0});  // failed
    // let mut a = Box::new(A{a:0, b:0});  // success 
    // let mut a = A{a:0, b:0};       // success
    
    let aa = &mut a.a; 
    let ab = &mut a.b; 
    
    println!("{} {}", aa, ab);
    
}


/*
Compiler output:
Line 14, Char 19: cannot borrow `a` as mutable more than once at a time (solution.rs)
   |
13 |     let aa = &mut a.a;
   |                   - first mutable borrow occurs here
14 |     let ab = &mut a.b;
   |                   ^ second mutable borrow occurs here
15 |
16 |     println!("{} {}", aa, ab);
   |                       -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
error: could not compile `prog` due to previous error
*.

It's because the field access goes through the DerefMut trait. Your code is short-hand for this:

use std::ops::DerefMut;
use std::mem::ManuallyDrop;

fn main() {
    struct A {
        a: i32, 
        b: i32,
    }
    
    let mut a = ManuallyDrop::new(A{a:0, b:0});  // failed
    // let mut a = Box::new(A{a:0, b:0});  // success 
    // let mut a = A{a:0, b:0};       // success
    
    let aa = &mut DerefMut::deref_mut(&mut a).a; 
    let ab = &mut DerefMut::deref_mut(&mut a).b; 
    
    println!("{} {}", aa, ab);
}

To fix it, you want this instead:

let a_ref = &mut *a;
let aa = &mut a_ref.a;
let ab = &mut a_ref.b;

which is short-hand for

let a_ref = DerefMut::deref_mut(&mut a);
let aa = &mut a_ref.a;
let ab = &mut a_ref.b;

Here, the type of a_ref is &mut A.

You will get the same error if you replace ManuallyDrop with any other wrapper that uses DerefMut to access the inner value, except for Box because Box is special.

3 Likes

Is there any plan of rust to make wrappers like ManuallyDrop to be special too :joy:
It's strange to see Box have some features while other wrappers do not.

No, ManuallyDrop will not become special in the same way as Box.

Why would you want more types to be special? If anything, this is an argument for making Box less of a special-case.

I just want a consistent behavior

Why would you want more types to be special? If anything, this is an argument for making Box less of a special-case.

Yes. Maybe removing special behaviors from Box is also an option. But in this case, I think what Box did is more convenient for us. Making all objects with DerefMut act the same as Box looks better.

But for general DerefMut implementations it's impossible. Consider this:

use core::fmt::Debug;
use core::ops::Deref;
use core::ops::DerefMut;

struct NoisyDeref<T: Debug>(T);

impl<T: Debug> Deref for NoisyDeref<T> {
    type Target = T;
    fn deref(&self) -> &T {
        println!("Deref immutably to {:?}", self.0);
        &self.0
    }
}

impl<T: Debug> DerefMut for NoisyDeref<T> {
    fn deref_mut(&mut self) -> &mut T {
        println!("Deref mutably to {:?}", self.0);
        &mut self.0
    }
}

fn main() {
    #[derive(Debug)]
    struct A {
        a: i32,
        b: i32,
    }

    let mut a = NoisyDeref(A { a: 0, b: 0 });

    let a = &mut *a;
    let aa = &mut a.a;
    let ab = &mut a.b;

    println!("{} {}", aa, ab);
}

Playground

If NoisyDeref were to work like Box, then println! inside the second dereference would clearly alias with the first handed-out reference.

1 Like

You may be interested how special the Box's Deref impl is.

fn deref(&self) -> &T {
    &**self
}
3 Likes

Thank you. But, the implemention is so confusing, at least to me.

I found the issue of why Box is special is well explained in this blog:

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.