rustlockingmutex

Why is the lock in Rust held throughout the `if let` block?


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?


Solution

  • 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. temporaries are only ever held for the span of the pattern
    2. temporaries are held until the end of the body

    (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: