2

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?

Ivan Ivanyuk
  • 318
  • 2
  • 11
  • 2
    I'd say either worked and which ever makes the code clearest for the particular case should be favoured. In this case `drop(some_bool)` would release the lock. – Holloway Jul 06 '23 at 10:14
  • 2
    Drop order is guaranteed in Rust, so the compiler cannot arbitrarily decide to drop something earlier. Wrapping the lock in a scope or manually dropping it does essentially the same thing (barring any other effects the nested scope might have). – isaactfa Jul 06 '23 at 10:15

1 Answers1

4

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.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77