waker-fn
Convert any Rust closure into a std::task::Waker without hand-rolling the Wake trait.
Repository Health
Technical Analysis
waker-fn is a tiny utility crate from the smol-rs ecosystem that solves one narrow, recurring problem in async Rust: turning a plain closure into a std::task::Waker. Implementing a Waker by hand normally means writing a RawWakerVTable or implementing the Wake trait over an Arc-wrapped type, which is boilerplate every executor or futures-adjacent crate ends up repeating.
waker-fn collapses that down to a single call: waker_fn(|| { ... }) returns a working Waker that invokes the closure whenever it’s woken. It’s #![no_std] (using alloc for the Arc) and #![forbid(unsafe_code)], so it adds no unsafe code and works in embedded or otherwise no_std async contexts. An optional portable-atomic feature swaps in the portable-atomic-util crate’s Arc/Wake so the crate also works on targets without native atomic support.
It’s deliberately minimal — one file, one public function — and is consumed as a low-level building block by other crates in the smol async runtime family (and beyond) rather than used directly by application code in most cases.
What You Get
waker_fnfunction - a single call that turns anyFn() + Send + Sync + 'staticclosure into a workingstd::task::Waker- No unsafe code - the crate is
#![forbid(unsafe_code)], so wrapping a closure never requires writing or auditing unsafe waker vtables no_stdsupport - built as#![no_std]using onlyalloc::sync::Arc, so it works in embedded and otherno_stdasync environmentsportable-atomicfeature flag - swaps inportable-atomic-util’sArc/Wakeimplementation for targets lacking native atomic support- Zero configuration - no setup, traits to implement, or types to define; import the function and call it
Common Use Cases
- Writing a custom async executor - convert a task-wakeup callback (e.g. re-queue a task ID) into a
Wakerwithout implementingWakeby hand - Testing futures and async code - build a throwaway
Wakerin a test that flips a flag or increments a counter to assert a future was woken - Bridging non-Rust wakeup signals into
Waker- wrap an FFI callback or hardware interrupt handler as a closure and expose it as a standardWaker - Implementing low-level futures combinators - construct ad-hoc wakers when composing or adapting futures outside of a full executor
Under The Hood
Architecture
The crate is a single file (src/lib.rs) exposing one public function, waker_fn<F: Fn() + Send + Sync + 'static>(f: F) -> Waker, which wraps the closure in a private Helper<F> tuple struct, puts it behind an Arc, and converts it to a std::task::Waker via Waker::from(Arc::new(Helper(f))). Helper<F> implements the standard library’s Wake trait (or, under the portable-atomic feature, portable_atomic_util::task::Wake) with wake and wake_by_ref both simply invoking the wrapped closure. The two feature-gated impl blocks are functionally identical, differing only in the receiver type (Arc<Self> vs. &Arc<Self>, the latter needed because portable_atomic_util::Arc as a receiver requires the unstable arbitrary_self_types feature). There is effectively no layering beyond this: the whole crate is a direct, minimal adapter onto std’s waker machinery.
Tech Stack
Written in Rust (edition 2018, MSRV 1.51), #![no_std] by default using only alloc::sync::Arc and core::task::Waker/Wake. The sole dependency, portable-atomic-util, is optional and gated behind the portable-atomic feature for targets without native atomic support. There is no runtime framework dependency — the crate is a primitive consumed by async runtimes and futures code (notably the smol ecosystem) rather than a framework itself. It ships purely as a published crates.io library with no build or deployment target beyond cargo build/cargo publish.
Code Quality
There are no dedicated unit test files, but the public function’s rustdoc example doubles as a doctest exercised via cargo test. CI (via reusable smol-rs/.github workflows) runs the test suite on nightly/beta/stable Rust plus a pinned MSRV build, cargo clippy --all-features --all-targets, cargo fmt checks, a weekly cargo-audit security scan, and — notably for a #![forbid(unsafe_code)] crate — cargo miri test under both the default and portable-atomic feature sets with strict-provenance and randomized-layout flags to catch undefined behavior. That combination of miri, clippy, fmt, and audit checks is disproportionately thorough for the crate’s size, and the forbid(unsafe_code) attribute is enforced at compile time rather than merely a convention.
API Design
The entire public surface is one function with an unmistakable name and a single generic bound (Fn() + Send + Sync + 'static), so there is effectively zero learning curve or setup boilerplate — call waker_fn(closure) and get back a standard Waker. The rustdoc carries a runnable example directly in the doc comment. The design trades any notion of configurability for total simplicity, which is the entire point: it exists specifically to eliminate the boilerplate of hand-writing a Wake implementation or raw waker vtable.