We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 8 of 25
`RawWaker` and `RawWakerVTable`: building a `Waker` with unsafe
Item 16.7 built a Waker from Arc<W> with no unsafe at all. That
convenience is a thin wrapper over the representation every runtime actually
uses, and this problem is that representation.
fn make_waker(state: Arc<Counter>) -> Waker
pub fn refcount_trace(clones: usize, wakes: usize) -> (usize, usize)
refcount_trace builds one Waker over a shared Counter, calls
wake_by_ref wakes times, then clones the waker clones times and disposes
of each clone — the even-indexed ones by wake() (which consumes), the
odd-indexed ones by drop(). It returns (observed wake count, final strong count of the Arc). Get the refcounting right and the strong count is always
exactly 2: the original state handle, plus the one owned by the raw
pointer inside the surviving Waker.
What a Waker really is
Two words. A *const () data pointer and a &'static RawWakerVTable — and
the vtable is four function pointers:
RawWakerVTable::new(clone, wake, wake_by_ref, drop)
That is why passing a Waker around is free, why it does not allocate, and
why it is not a Box<dyn Fn()>. It is also why building one is unsafe: the
compiler has no idea what your data pointer points at, so you are
promising that all four functions agree about it.
The convention that makes it work is the one Arc already uses:
-
the data pointer is
Arc::into_raw(state), and it owns one strong count; -
clonemust produce a second pointer that also owns one, so it increments; -
wakeconsumes the count it was given — reconstruct theArcand let it drop; -
wake_by_refmust not consume it — reconstruct theArcinside aManuallyDropso the count survives; -
dropreleases the count without doing any work.
Get clone wrong by one and you have a use-after-free. Get drop wrong by
one and you have a leak. Neither is a compile error, and under -O neither
is reliably a crash. That is the real lesson of this item: unsafe moves
the proof obligation from the compiler to you, and nothing checks your work.
The safety contract, precisely
From the RawWaker docs: all four functions must be thread-safe, even
though RawWaker is itself neither Send nor Sync. The reason is that
Waker is Send + Sync, and it can be handed to another thread and invoked
there through a shared reference. Arc‘s atomic counters are what make the
usual implementation satisfy this; a Rc-based one would be unsound even
though it compiles.
Rust 2024 makes you say unsafe twice
Since edition 2024, unsafe_op_in_unsafe_fn is on by default: marking a
function unsafe no longer makes its whole body an implicit unsafe block.
Declaring “callers must uphold X” and performing an unchecked operation are
two separate acts, and you now write both. Inside each of your unsafe fn
vtable entries you still need an explicit unsafe { .. } around the
Arc::from_raw call.
The starter forgets the outer one and gets E0133: “call to unsafe function
Waker::from_raw is unsafe and requires unsafe block”.
Pointer casts, idiomatically
ptr.cast::<Counter>() rather than ptr as *const Counter. Same machine code,
and it cannot silently change mutability or discard a const the way an as
cast can.
Which should you actually use?
Wake from item 16.7. Always, in real code. This item exists so that when you
read tokio’s RawWakerVTable — or a no_std executor that cannot afford
Arc — you recognise the shape and can check the refcount argument yourself.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.