Borrow checker error on nested mutex pattern

I have the following code for which borrow checking fails.
I know the nested mutex does not make sense, but I'm designing a XXXguard pattern where nested use should be possible, and having trouble getting past the borrow checker.

Access to a & b is orthogonal so I think it should not be a problem (It indeed isn't a problem when I use the comment out code).
I wonder why the borrow checker has to complain about it, and is there a way I can design XXXguard so that I can use recursively?

use std::sync::Mutex;

struct MyStruct {
    a: Mutex<i32>,
    b: i32,
}

impl MyStruct {
    pub fn new() -> Self {
        Self {
            a: Mutex::new(0),
            b: 0,
        }
    }
}

fn main( ) {
    let x = Mutex::new(MyStruct::new());
    let mut xg = x.lock().unwrap();

    // let mut xg = MyStruct::new(); // This is okay
    let mut a = xg.a.lock();
    xg.b = 0;
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
warning: unused variable: `a`
  --> src/lib.rs:20:13
   |
20 |     let mut a = xg.a.lock();
   |             ^ help: if this is intentional, prefix it with an underscore: `_a`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: variable does not need to be mutable
  --> src/lib.rs:20:9
   |
20 |     let mut a = xg.a.lock();
   |         ----^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0502]: cannot borrow `xg` as mutable because it is also borrowed as immutable
  --> src/lib.rs:21:5
   |
20 |     let mut a = xg.a.lock();
   |                 -- immutable borrow occurs here
21 |     xg.b = 0;
   |     ^^ mutable borrow occurs here
22 | }
   | - immutable borrow might be used here, when `a` is dropped and runs the destructor for type `Result<MutexGuard<'_, i32>, PoisonError<MutexGuard<'_, i32>>>`

For more information about this error, try `rustc --explain E0502`.
warning: `playground` (lib) generated 2 warnings
error: could not compile `playground` due to previous error; 2 warnings emitted

Borrow splitting, the feature that allows multiple fields to be used mutably at the same time, is somewhat fragile: it will work with &mut MyStruct references, but it will not work with MutexGuard<'_, MyStruct> smart pointers. The solution is to convert xg from a smart pointer into an ordinary reference by reborrowing it with &mut * (Rust Playground):

 fn main() {
     let x = Mutex::new(MyStruct::new());
-    let xg = x.lock().unwrap();
+    let xg = &mut *x.lock().unwrap();
     let _a = xg.a.lock();
     xg.b = 0;
 }

Alternatively, if you want to control when the original MutexGuard is dropped, you can write a separate let xg_ref = &mut *xg; line, and use xg_ref when accessing the fields.

3 Likes

The previous answer gives a solution but no explanation to the question quoted above.

There is no magic, and borrow splitting isn't "fragile". The error is simply because the mutex guard gives access to the whole wrapped value via its Deref::deref()/DerefMut::deref_mut() impls. In particular, these functions have the signature

fn deref(&self) -> &Self::Target;
fn deref_mut(&mut self) -> &mut Self::Target;

which means that once you got to the guarded value, you must have necessarily borrowed the whole guard (self), not only a single field of the wrapped value, so if you try to deref_mut() again, it won't work due to the other, already-existing borrow.

Using an explicit borrow and splitting that works because the compiler can reason about a single mutable reference, since that's a built-in primitive type.

1 Like

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.