Seems you are actually reverting #6 and go back to the "base pointer" approach instead of a union.
Personally I'd prefer the former since it makes more sense and avoid the std Waker being present in the FFI struct. It is kind of wired, and, as you said, breaks abi_stable compatibility. But the union approach surely makes the code more clear.
cc @PonasKovas
I used to consider another approach that decompose the Waker back into two opaque pointers, maybe passing through FFI, and recompose them back. This could eliminate the pointer-inheritance dance and the Boxing (so we could be no_std).
But currently std only expose (data_ptr, vtable) -> RawWaker but not reverse (RawWaker is repr(transparent) of Waker, so they can be safely transmuted). It would definitely requires some layout-assumption[1] to achieve it. But the core of this crate is to avoid this.
[1]: RawWaker's doc said: It consists of a data pointer and a virtual function pointer table (vtable) that customizes the behavior of the RawWaker. So we it must consists of two pointer, and there are only two possible layout, (data_ptr, vtable) and (vtable, data_ptr). We can use a compile-time condition to know which is one correct for the current compiling unit, and decompose it into a deterministic FfiWaker.