waker_tables
A tiny crate for manual vtables and RawWaker plumbing.
waker_tables gives you two building blocks:
-
UnsafeWakeable+RawWakerVTablehelpers
For when you want astd::task::Wakerbacked by your own type (usuallyArc<T>) without fightingRawWakerboilerplate. -
UnsafeWakeableTask+UnsafeWakeableVTable+ registry
For when you want a cheap, type-erased, manually-polled “task object” with function pointers (poll/wake/drop) — perfect for custom schedulers, runtimes, staged pipelines, actor systems, or any “task graph” that is notFuture-shaped.
This crate is intentionally low-level: it gives you correct pointer/refcount wiring and fast type erasure, while you own scheduling, queues, waking policy, and invariants.
Why this exists
If you’re building anything like:
- a custom runtime / scheduler
- a stage graph / orchestration engine
- a task system that isn’t
Future(or doesn’t want trait objects everywhere) - a “fat pointer” task handle that can be passed across subsystems cheaply
- a registry keyed by protocol version (segment/kind/version)
…then you eventually want:
- stable function-pointer vtables
- raw-pointer tasks with explicit drop and wake routes
- ability to store tasks in intrusive queues / slabs / rings without dynamic dispatch costs
waker_tables is the “do it once, do it correctly” layer.
Safety model (read this)
There are two “unsafe contracts” in this crate:
1) UnsafeWakeable (RawWaker contract)
You promise that the *const Self used by the vtable obeys Arc::into_raw / Arc::from_raw refcount rules.
clone_ptrmust produce a pointer owning +1 strongArcrefdrop_ptrmust balance exactly one strong refwakeconsumes the ref (likeWaker::wake)wake_by_refmust not consume the original ref
This is only about ownership and refcount correctness, not scheduling.
2) UnsafeWakeableTask (manual task vtable)
You promise that:
- the
data: *mut ()matches the vtable you call it with - you uphold any exclusivity invariants around polling (like you would with futures)
This crate intentionally does not enforce a scheduler model — that’s your job.
Crate overview
-
UnsafeWakeable— wire your own type intoRawWakerVTable -
derive_arc_unsafe_wakeable!— convenience macro for theArc<T>case -
declare_raw_waker_vtable!— generate a'staticRawWakerVTablefor your type -
waker_from_arc— build a realWakerfromArc<T>+ vtable -
UnsafeWakeableTask— “task object” contract:poll/wake/drop_in_place -
derive_unsafe_wakeable_vtable!— generate a'staticfunction-pointer vtable -
UnsafeWakeablePtr— thin “fat pointer” pair (data + &'static vtable) -
VTableRegistry+VTableKey— segment/kind/version mapping to vtables -
RegisteredTask— safer wrapper coupling aVTableKeywith a task pointer
Quick start
Add the crate:
[dependencies]
waker_tables = "0.x"
Example 1: A Waker backed by your Arc<T> (RawWaker route)
This is the “I want Waker to call my scheduler” path.
use ;
use Waker;
use ;
// Wire Arc<MyTask> into the UnsafeWakeable contract.
derive_arc_unsafe_wakeable!;
// Generate the vtable once.
declare_raw_waker_vtable!;
When to use this: you have a scheduler that is Waker-driven (polling futures, bridging into async ecosystems, etc.).
Example 2: A manual “task object” with a function-pointer vtable
This is the “I want a fast erased task pointer” path.
use Poll;
use ;
use ;
unsafe
// Generate a static vtable for the type.
derive_unsafe_wakeable_vtable!;
When to use this: you’re building your own runtime that does not want dyn Future overhead, or you want “task handles” that are cheap to pass around.
Example 3: Register task vtables by protocol key (segment/kind/version)
This gives you a “dispatch table” for heterogeneous tasks across subsystems.
use Poll;
use ;
;
;
unsafe
unsafe
derive_unsafe_wakeable_vtable!;
derive_unsafe_wakeable_vtable!;
When to use this: you have multiple “job families” and want versioned rollout / protocol evolution without generic soup.
Example 4: A tiny scheduler sketch (single-thread “run queue”)
This shows how UnsafeWakeableTask can drive a basic scheduler. It’s intentionally minimal.
use VecDeque;
use Poll;
use ;
unsafe
derive_unsafe_wakeable_vtable!;
Performance notes
UnsafeWakeablePtris basically a manually-managed fat pointer: two machine words.UnsafeWakeableVTableis a set of three function pointers:poll/wake/drop.- Everything is designed for fast dispatch with predictable branches.
If you benchmark, remember:
- cold runs can include I/O/page-fault noise
- pinning tasks/threads and stable CPU governor matters
mmapworkloads fluctuate depending on page cache state
Common pitfalls
-
Mixing pointer + vtable: calling the wrong vtable for the wrong type is UB.
UseRegisteredTaskto coupleVTableKeywith the pointer. -
Polling concurrently: if your task is not internally synchronized, you must ensure exclusive polling (just like futures).
-
RawWaker refcounts: if you implement
UnsafeWakeablemanually, the only acceptable mental model is:
“every pointer is anArc<T>raw pointer; clone increments; drop decrements.”
When you should NOT use this crate
If you are perfectly happy with:
Box<dyn Future<Output = ()>>tokio::spawnfutures::task::ArcWake- normal
dyn Traitobject dispatch
…then you probably don’t need waker_tables.
This crate is for people who want to own their runtime boundaries.
License
MIT & Apache-2.0