Call Mutex.lock()
after if let
. This lock will be held inside the whole if let
block.
If we lock again in the if let
block. It's deadlock. This also happen for RwLock
.
use std::sync::Mutex;
fn main() {
let number = Mutex::new(Some(1));
if let Some(i) = number.lock().unwrap().take() { // first lock.
println!("{}", i);
assert!(number.lock().unwrap().is_none()); // deadlock here.
}
println!("end")
}
My question is that. Does it make sense to hold the first lock in the whole if let
block? For my understanding, it should be released after take()
. Am I right?
Why is the lock in Rust held throughout the
if let
block?
It's held through the block because that's how if let
temporary scopes are defined:
Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is one of the following:
- The pattern-matching condition and consequent body of if let
This is commonly fixed by introducing "move fence" via a block:
use std::sync::Mutex;
fn main() {
let number = Mutex::new(Some(1));
if let Some(i) = {number.lock().unwrap().take()} { // first lock.
println!("{}", i);
assert!(number.lock().unwrap().is_none()); // deadlock here.
}
println!("end")
}
Does it make sense to hold the first lock in the whole if let block? For my understanding, it should be released after take(). Am I right?
The borrow checker doesn't influence temporary lifetimes, so in Rust's model there are only two choices:
(1) would mean
if let Some(i) = number.lock().unwrap().as_ref() {
println!("{i}");
}
can never work, because the temporary MutexGuard
will be dropped before the body can run, invalidating the borrow. That was considered a useful thing to support, so the designers of Rust went with (2). But that has the pitfall you discovered.
References / more on the subject: