Search code examples
rustmutexraii

What does the parking_lot documentation mean when it says 'allow raw (un)locking without a RAII guard object'?


The docs for parking_lot say:

  1. Mutex and RwLock allow raw unlocking without a RAII guard object.
  2. Mutex<()> and RwLock<()> allow raw locking without a RAII guard object.

There is no further mention of these features, what they mean and how to use them. What are some pointers or sample uses?


Solution

  • The Mutex API controls access to its data via a guard, which unlocks the Mutex when it goes out of scope. The Mutex owns its data and can enforce that it is only accessible through a MutexGuard when it is locked. Both std::sync::Mutex and parking_lot::Mutex are the same in this regard.

    However, parking_lot::Mutex also exposes its internals, which are a raw pointer to the data and a RawMutex. A RawMutex is just a lock, which does not control access to the data, but just tracks the state of the lock.

    One reason to use RawMutex might be for times when it is very inconvenient to keep a MutexGuard in scope, and you are prepared to manage the lock status yourself. This is more likely in a library that defines new synchronization primitives or smart pointers rather than in application code, but you might also find it useful if you were mechanically translating existing C/C++ code to Rust.

    By way of a simple example, these functions do the same thing as each other, but one uses the unsafe RawMutex:

    use parking_lot::{Mutex, lock_api::RawMutex as _};
    
    fn update_mutex(mutex: &Mutex<i32>) {
        let mut guard = mutex.lock();
        *guard = 2;
        // guard goes out of scope here, causing the Mutex to be unlocked
    }
    
    fn update_mutex_raw(mutex: &Mutex<i32>) {
        let raw_mutex = unsafe { mutex.raw() };
        let data = mutex.data_ptr();
        raw_mutex.lock();
        unsafe { 
            *data = 2;
            // need to manually unlock the RawMutex
            raw_mutex.unlock();  
        };
    }
    

    RawMutex.unlock() is unsafe because it would trigger Undefined Behaviour to call it when the murex is not locked. Dereferencing the data pointer twice at once would be Undefined Behaviour too, so it's up to you to make sure you don't, by honouring the lock state of the RawMutex.

    As always, when using unsafe functions, read the documentation carefully and make sure you thoroughly undersand the invariants that you must preserve in order to avoid Undefined Behaviour.