Search code examples
rustpremature-optimization

Does wrapping lock into scope change when lock will be released?


In code

fn do_something_under_lock(some_bool_mutex: &Mutex<bool>) {
    do_something_before_lock();

    let some_bool = some_bool_mutex.lock().unwrap();
    do_something_with_some_bool(some_bool);

    do_something_after_lock();
}

will lock be released only after do_something_after_lock();?(or maybe compiler can prove that we don't need long lock time and decrease it?)

If lock will be released only after do_something_after_lock();, can we release lock earlier by wrapping lock() into own scope, like

fn do_something_under_lock_in_scope(some_bool_mutex: &Mutex<bool>) {
    do_something_before_lock();

    {
        let some_bool = some_bool_mutex.lock().unwrap();
        do_something_with_some_bool(some_bool);
    }

    do_something_after_lock();
}

Is wrapping into scope the best way to tell compiler what we want, or should we use some other way, like drop or something?


Solution

  • The lock will be released after do_something_after_lock(). This is considered observable behavior, and therefore, the compiler is not allowed to change that.

    Wrapping the lock in scope and using drop() are both fine. I prefer the drop() version, but this is subjective. One thing that is important to note, however, is that in async functions the lock is considered alive even if you drop() it (it will be released, but the compiler will still consider its type as part of the generator type), so if the lock must not be held across .await points (for example because it is not Send), you must use a block and drop() is not enough. For instance, the following code errs:

    async fn foo(mutex: &Mutex<i32>) {
        let _guard = mutex.lock().unwrap();
        drop(_guard);
        
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
    
    tokio::spawn(foo(mutex));
    

    You need to use a block:

    async fn foo(mutex: &Mutex<i32>) {
        {
            let _guard = mutex.lock().unwrap();
        }
        
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
    
    tokio::spawn(foo(mutex));
    

    This is expected to improve in future Rust versions.