rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.68k stars 12.75k forks source link

Nested mutex guards with dropping and re-assigning confuse the borrow checker #93770

Closed sdroege closed 1 year ago

sdroege commented 2 years ago

I tried this (minimalized) code:

use std::sync::Mutex;

struct A {
    b: Mutex<B>,
}

struct B {
    c: Mutex<C>,
}

struct C {
    x: i32,
}

fn main() {
    let a = A {
        b: Mutex::new(B {
            c: Mutex::new(C { x: 1 }),
        }),
    };

    let mut b = a.b.lock().unwrap();
    let mut c = b.c.lock().unwrap();

    drop(c);
    drop(b);

    b = a.b.lock().unwrap();
    c = b.c.lock().unwrap();

    println!("{}", c.x);
}

I expected to see this happen: code compiles fine

Instead, this happened:

error[E0505]: cannot move out of `b` because it is borrowed
  --> src/main.rs:26:10
   |
23 |     let mut c = b.c.lock().unwrap();
   |                 - borrow of `b` occurs here
...
26 |     drop(b);
   |          ^ move out of `b` occurs here
...
29 |     c = b.c.lock().unwrap();
   |     - borrow might be used here, when `c` is dropped and runs the `Drop` code for type `MutexGuard`

error[E0506]: cannot assign to `b` because it is borrowed
   --> src/main.rs:28:5
    |
23  |     let mut c = b.c.lock().unwrap();
    |                 - borrow of `b` occurs here
...
28  |     b = a.b.lock().unwrap();
    |     ^ assignment to borrowed `b` occurs here
29  |     c = b.c.lock().unwrap();
    |     - borrow might be used here, when `c` is dropped and runs the `Drop` code for type `MutexGuard`
    |
    = note: borrow occurs due to deref coercion to `B`
note: deref defined here
   --> /home/slomo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sync/mutex.rs:470:5
    |
470 |     type Target = T;
    |     ^^^^^^^^^^^^^^^^

Removing the usage of c in the very end (the println!) makes the code compile as expected, but that's of course not useful:

--- a/src/main.rs
+++ b/src/main.rs
@@ -28,5 +28,5 @@ fn main() {
     b = a.b.lock().unwrap();
     c = b.c.lock().unwrap();

-    println!("{}", c.x);
+    //println!("{}", c.x);
 }

Similarly not dropping b and reassigning it later also compiles fine (i.e. the dropping/reassigning of c can stay around):

--- a/src/main.rs
+++ b/src/main.rs
@@ -23,9 +23,9 @@ fn main() {
     let mut c = b.c.lock().unwrap();

     drop(c);
-    drop(b);
+    //drop(b);

-    b = a.b.lock().unwrap();
+    //b = a.b.lock().unwrap();
     c = b.c.lock().unwrap();

     println!("{}", c.x);

The borrow of b should've been released by the drop(c) but that's apparently not the case for some reason.

Meta

rustc --version --verbose:

rustc 1.58.1 (db9d1b20b 2022-01-20)
binary: rustc
commit-hash: db9d1b20bba1968c1ec1fc49616d4742c1725b4b
commit-date: 2022-01-20
host: x86_64-unknown-linux-gnu
release: 1.58.1
LLVM version: 13.0.0
GuillaumeGomez commented 2 years ago

cc @pnkfelix

apiraino commented 2 years ago

Issue was mentioned during T-compiler weekly meeting on Zulip

Dylan-DPC commented 1 year ago

This code now compiles and gives you the designated result.