Get waker vtable derived from `Wake` trait

Tracking Issue for `waker_getters` · Issue #96992 · rust-lang/rust · GitHub as well as Stabilize `waker_getters` by kevinmehall · Pull Request #129919 · rust-lang/rust · GitHub have been merged today.

It allows notably to retrieve the vtable of a Waker, and that can be used for example to match on it and identify the current async runtime, as presented in this comment. When I implemented asynchronous support in PyO3, I was also needing this feature (and I had to use a dirty hack based on TLS while waiting for it).

However, to avoid useless unsafe code, I'm using the Wake trait to craft my coroutine runtime Waker, and there's the rub... I cannot use waker_getters feature because I don't have access to the vtable of the wakers derived from Arc<W> where W: Wake. The waker_getters feature is quite unusable with Arc<W> where W: Wake.

I tried two times (here and here) to highlight this problem in the issue, proposing two different ways of making the feature compatible with the Wake trait, but my attempts were kind of ignored. So I make a new attempt here.

A possible solution to the problem could be this implementation:

impl<W: Wake + Send + Sync + 'static> Arc<W> {
    const fn waker_vtable() -> &'static RawWakerVTable { /*...*/ }
}

It could also be done with an extension trait, something like:

pub trait WakeExt: Wake + Send + Sync + 'static {
    /// Vtable of waker derived from `Arc<Self>`.
    fn waker_vtable() -> &'static RawWakerVTable { /*...*/ }
    fn try_from_waker(waker: Waker) -> Result<Arc<Self>, Waker> { /*...*/ }
    fn try_from_waker_ref(waker: &Waker) -> Result<&Self, &Waker> { /*...*/ }
}
impl<W: Wake + Send + Sync + 'static> WakeExt for W {}

Unfortunately, this doesn't work like you want it to, and would be a massive footgun to expose in this way. Even though you have const fn returning &'static, since the static storage relies on constant promotion, calling the function twice may return a reference to different addresses[1]. If the address is semantically meaningful, you must use a static (or only operationally create the value a single time). And it's not possible to have a generic static (nor to create references to static within const fn).


  1. Within a single codegen unit, the value is most likely to only exist once. However, across multiple codegen units, the function may get monomorphized more than once for one of various different reasons, creating multiple compiled versions of the function each with their own copy of the promoted static data. In the worst case, these copies are in different lazy loaded dynamic libraries and fundamentally can't be unified with each other. ↩︎

1 Like

And conversely, though it is pretty unlikely for a waker, if two functions have identical behavior, some optimization modes might unify them, and then the vtables might get unified too. So you could have false negatives and false positives.

3 Likes

Thank you for your answers, which perfectly explains the problem. So I guess I will have to define my own vtable with some unsafe, but I'm fine with this justification.

(NOT A CONTRIBUTION)

On the other hand, I believe RawWakerVTable could have a constructor that is parameterized by W: Wake and takes no arguments. Users could use this to define the static for their Wake type without writing any unsafe code.