Skip to content

ACP: Add Waker::with_arc_wake #621

@orlp

Description

@orlp

Proposal

Problem statement

The Rust standard library provides the Wake trait to allow you to create Wakers 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions