2

The following code deadlocks because the mutex is not unlocked after the last use of v:

use std::sync::{Arc,Mutex};

fn main() {
    let a = Arc::new(Mutex::new(3));
    let mut v = a.lock().unwrap();
    *v += 1;
    println!("v is {v}");
    // drop(v);
    let b = Arc::clone(&a);
    std::thread::spawn(move || {
        let mut w = b.lock().unwrap();
        *w += 1;
        println!("w is {w}");
    }).join().unwrap();
}

The fix is to uncomment the explicit drop(v). Why does compiler not automatically drop v after its last use?

In contrast, the Rust compiler knows to correctly drop v early in the following case:

fn main() {
    let mut a = 3;
    let v = &mut a;
    *v += 1;
    println!("v is {v}");
    let w = &mut a;
    *w += 1;
    println!("w is {w}");
}

This behavior seems natural, I would expect the compiler to do the same above.

Aditya
  • 80
  • 6
  • because you are specifically in the main's scope so `v` will be dropped at the `}` of main. If you explicitly want to drop v, your approach of `drop(v)` is correct. or you could `{ }` the references to `v` – Ahmed Masud Jan 15 '23 at 18:46
  • Thank you for the response, but then why is v dropped automatically in the second code snippet before the scope of main ends? I did not create any block scope in that one either. – Aditya Jan 15 '23 at 18:47
  • See the rules of how drop works here .... https://doc.rust-lang.org/reference/destructors.html – Ahmed Masud Jan 15 '23 at 18:49
  • Technically v & w in the second example also shouldn't co-exist (two mutable references to the same thing). If you interchange line 4 & 5 it fails too. This also applies to complex containers. The answer by kmdreko clarifies this: The second case works because borrow checker is smart with references, while explicit drops occur at the end of scope. – Aditya Jan 15 '23 at 18:56

1 Answers1

7

Values are dropped when their scope ends, not after their last use. What may be confusing you is that the borrow checker knows references are inconsequential after their last use, and thus considers their lifetimes differently for the purposes of enforcing Rust's referential guarantees.

Technically v in the second example is not dropped until the end of the scope either, but there is no drop logic for references. See What are non-lexical lifetimes?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • This was on point, thank you so much! – Aditya Jan 15 '23 at 18:56
  • Is it possible to fully explain this with NLL only? Don't you also need knowledge about how `MutexGuard` implementing `Drop` changes things? (I can't find a good article right now, besides the [rustonomicon](https://doc.rust-lang.org/nomicon/dropck.html).) – Caesar Jan 16 '23 at 00:08
  • @Caesar I wasn't trying to explain it all with NLL, only that the borrow checker doesn't quite use the same rules as when values are dropped. Certainly knowing how RAII wrappers use `Drop` to run automatic cleanup logic helps, but I'm not sure why the answer should be specific to `MutexGuard`. – kmdreko Jan 16 '23 at 00:29