-
Notifications
You must be signed in to change notification settings - Fork 22
Description
Proposal
Problem statement
The Rust standard library provides the Wake
trait to allow you to create Waker
s without unsafe code. This is nice, but there is no way to create a Waker
from an &Arc<W>
without cloning the Arc
first. However, this is unnecessary, this clone
could happen on the first time the Waker
is cloned instead (and thus may be avoided if the Waker
isn't cloned at all).
Motivating examples or use cases
I have some waker: Arc<W>
inside a struct I re-use every time I wish to block_on
using this struct. The code might look something like this:
pub fn block_on<F: Future>(&self, fut: F) -> <F as Future>::Output {
let mut fut = pin!(fut);
let mut cx = Context::from_waker(self.waker.clone());
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(output) => return output,
Poll::Pending => self.waker.block_until_notified(),
}
}
}
Note how we have to call self.waker.clone()
every time, regardless of whether we end up blocking or not.
Solution sketch
We could add the function Waker::with_arc_wake
which uses a closure to guarantee the &Waker
it provides
does not outlive the arc_wake
, without having to call clone:
/// Creates a temporary [`Waker`] using the given [`Wake`] implementation.
fn with_arc_wake<R, W: Wake + Send + Sync + 'static, F: FnOnce(&Waker) -> R>(
arc_wake: &Arc<W>,
f: F,
) -> R {
// SAFETY: we create a clone of the Arc without incrementing the refcount.
// This is safe because we never drop this clone and the lifetime of
// arc_wake guarantees the Arc will not die prematurely either.
let tmp = ManuallyDrop::new(Waker::from(unsafe { core::ptr::from_ref(arc_wake).read() }));
f(&tmp)
}
With this function the above block_on
could be written as such:
pub fn block_on<F: Future>(&self, fut: F) -> <F as Future>::Output {
let mut fut = pin!(fut);
Waker::with_arc_wake(&self.waker, |waker| {
let mut cx = Context::from_waker(waker);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(output) => return output,
Poll::Pending => self.waker.block_until_notified(),
}
}
})
}
No more premature cloning.
Alternatives
The futures
crate uses the function waker_ref
returning a WakerRef<'a>
to achieve the same effect. While more flexible than with_arc_wake
it does add an extra type to the standard library which is quite a bit of extra surface to document, open questions, etc. In all use-cases I could think of with_arc_wake
achieves the same result with a smaller API.
One could of course also implement this yourself using unsafe
code, but as the point of the Wake
trait is to provide a safe alternative to RawWaker
, it would be nice if using it optimally also doesn't involve unsafe
code.
Links and related work
https://docs.rs/futures/latest/futures/task/fn.waker_ref.html
https://docs.rs/futures/latest/futures/task/struct.WakerRef.html