Synchronization Primitives

Read and Write Guards

RwLockReadGuard<'a, T> and RwLockWriteGuard<'a, T> are the RAII smart pointer guards returned by RwLock::read() and RwLock::write(). They enforce at compile time that readers only receive shared &T access while writers receive exclusive &mut T access, and they release their respective read or write lock automatically in Drop.

Comparing RwLockReadGuard and RwLockWriteGuard

Property RwLockReadGuard<'a, T> RwLockWriteGuard<'a, T>
Acquired via .read() / .try_read() .write() / .try_write()
Implements Deref<Target = T>? Yes (yields &T) Yes (yields &T)
Implements DerefMut<Target = T>? No (compile error E0596 on mutation) Yes (yields &mut T)
Implements Send? No (!Send) No (!Send)
Implements Sync? Sync if T: Sync Sync if T: Sync
Poisons RwLock on panic? No (read-only access cannot corrupt T) Yes (marks RwLock as poisoned)

Same-Thread Upgrade and Downgrade Rules (Avoiding Self-Deadlock!)

In std::sync::RwLock, there is no atomic upgrade from a RwLockReadGuard to a RwLockWriteGuard while keeping the read guard alive!

If a thread holds an active RwLockReadGuard and calls rwlock.write() on the same thread without dropping the read guard first, the thread immediately deadlocks—because .write() waits for all active readers (including the current thread's own RwLockReadGuard!) to finish!

Same-Thread Upgrade Deadlock Trap:

  let read_guard = rwlock.read().unwrap();     // readers = 1
  if needs_update(&*read_guard) {
      let write_guard = rwlock.write().unwrap(); // ❌ DEADLOCK! Waits for `read_guard` to drop!
  }

Safe Upgrade Pattern (Drop Read Guard -> Acquire Write Guard -> Re-check Condition!):
  let should_update = {
      let read_guard = rwlock.read().unwrap();
      needs_update(&*read_guard)
  }; // <-- `read_guard` dropped here (readers = 0)!

  if should_update {
      let mut write_guard = rwlock.write().unwrap();
      // Re-check condition in case another writer ran between dropping read_guard and acquiring write_guard!
      if needs_update(&*write_guard) {
          perform_update(&mut *write_guard);
      }
  }
use std::collections::HashMap;
use std::sync::RwLock;

struct TokenCache {
    tokens: RwLock<HashMap<String, u64>>,
}

impl TokenCache {
    fn new() -> Self {
        Self {
            tokens: RwLock::new(HashMap::new()),
        }
    }

    fn get_or_insert(&self, user: &str, default_id: u64) -> u64 {
        // 1. Fast path: check under a shared `RwLockReadGuard`:
        if let Some(&id) = self.tokens.read().unwrap().get(user) {
            return id;
        }
        // Notice the temporary `RwLockReadGuard` above is dropped at the end of the `if let`!

        // 2. Slow path: acquire an exclusive `RwLockWriteGuard` and use `.entry()`
        //    to re-check in case another thread inserted `user` in the meantime:
        let mut write_guard = self.tokens.write().unwrap();
        *write_guard.entry(user.to_string()).or_insert(default_id)
    }
}

fn main() {
    let cache = TokenCache::new();
    println!("first lookup (inserts):  {}", cache.get_or_insert("alice", 1001));
    println!("second lookup (cached):  {}", cache.get_or_insert("alice", 9999));
}
first lookup (inserts):  1001
second lookup (cached):  1001