#278 Jul 24, 2026

278. Condvar::wait_while — The Wait Loop You Keep Writing (and Getting Backwards)

A Condvar can wake up for no reason at all — so every correct wait is a loop. wait_while writes that loop for you.

The textbook pattern: a worker flips a flag under a Mutex, and a waiting thread sleeps until it’s flipped. The naive version is broken:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::sync::{Condvar, Mutex};

let lock = Mutex::new(false);
let cvar = Condvar::new();

// waiter — WRONG
let ready = lock.lock().unwrap();
if !*ready {
    let _ready = cvar.wait(ready).unwrap(); // may wake spuriously!
}

wait is allowed to return even though nobody called notify_one — a spurious wakeup. The OS primitives underneath make no stronger promise, so neither does Rust. Correct code re-checks in a loop:

1
2
3
4
let mut ready = lock.lock().unwrap();
while !*ready {
    ready = cvar.wait(ready).unwrap();
}

That works, but it’s boilerplate with two sharp edges: forget the loop and you have a heisenbug that only fires under load; shuffle the guard rebinding and it won’t compile. wait_while folds the whole dance into one call:

1
2
3
4
let ready = cvar
    .wait_while(lock.lock().unwrap(), |ready| !*ready)
    .unwrap();
assert!(*ready);

The predicate answers “should I keep waiting?” — it returns true to sleep, false to wake. That inversion is the one thing to internalize: you pass |ready| !*ready, not |ready| *ready. Read it as “wait while not ready.”

Full picture with a notifying thread:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::sync::{Arc, Condvar, Mutex};
use std::thread;

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);

thread::spawn(move || {
    let (lock, cvar) = &*pair2;
    *lock.lock().unwrap() = true;
    cvar.notify_one();
});

let (lock, cvar) = &*pair;
let ready = cvar
    .wait_while(lock.lock().unwrap(), |ready| !*ready)
    .unwrap();
assert!(*ready);

The predicate runs with the lock held, so it can inspect any state the mutex guards — a queue’s length, a counter, a shutdown flag — not just booleans. And because wait_while re-checks after every wakeup, spurious or real, the guard it returns is guaranteed to satisfy your condition.

There’s a deadline-aware sibling, wait_timeout_while, which additionally hands back a WaitTimeoutResult so you can tell “condition met” from “gave up.”

If all you’re guarding is “has this one value been initialized,” OnceLock::wait is the simpler tool. Condvar earns its keep when the condition is richer than init-once — and wait_while is how you use it without rediscovering spurious wakeups in production.

Stable since Rust 1.42.

← Previous 277. Release/Acquire — Publish Data Through a Flag, Not Just the Flag Next → 279. thread::park — Block a Thread Without Dragging In a Mutex