The core goal is to make async fn in trait and other RPITIT usable with dynamic dispatch via a proc macro, in a way similar to how it works with async_trait, but while continuing to allow static dispatch.
The ErasedAsyncIter trait is used to have the compiler generate a vtable for each concrete type that is used to create a DynAsyncIter. It is not part of the public interface. *mut dyn ErasedAsyncIter is a fat pointer.
Note the use of Ref and RefMut wrapper types (which would go in some support library) so that we can also have DynAsyncIter::from_ref and DynAsyncIter::from_ref_mut. These wrappers are slightly verbose, but due to their deref impls, can be reborrowed to create &DynMyTrait and &mut DynMyTrait respectively.
This code uses GATs instead of RPITIT in the original trait, since those weren't stable yet, but the same ideas should apply.
This code makes use of a union to do bit-twiddling on the data pointer, as a way of marking whether the underlying value is owned. This is not well-defined and should not be necessary, because we can instead have Ref<DynAsyncIter>'s field be ManuallyDrop<DynAsyncIter> EDIT: Actually we probably do need something like this if we have RefMut which gives out &mut DynAsyncIter; we just need to make it well-defined by using std pointer APIs.
The core goal is to make
async fn
in trait and other RPITIT usable with dynamic dispatch via a proc macro, in a way similar to how it works with async_trait, but while continuing to allow static dispatch.It core idea of how it works is described here: https://rust-lang.github.io/async-fundamentals-initiative/explainer/async_fn_in_dyn_trait/hardcoding_box.html
Given a trait like
Instead of using a type like
Box<dyn MyTrait>
which wouldn't compile today, we would have the proc macro create a typeDynMyTrait
with this interface:The struct itself would look something like this. A full explanation is in the links and in the exposition below.
A full example including macro output is here: https://github.com/nikomatsakis/dyner/blob/main/src/async_iter.rs. Notes:
ErasedAsyncIter
trait is used to have the compiler generate a vtable for each concrete type that is used to create aDynAsyncIter
. It is not part of the public interface.*mut dyn ErasedAsyncIter
is a fat pointer.Ref
andRefMut
wrapper types (which would go in some support library) so that we can also haveDynAsyncIter::from_ref
andDynAsyncIter::from_ref_mut
. These wrappers are slightly verbose, but due to their deref impls, can be reborrowed to create&DynMyTrait
and&mut DynMyTrait
respectively.and should not be necessary, because we can instead haveEDIT: Actually we probably do need something like this if we haveRef<DynAsyncIter>
's field beManuallyDrop<DynAsyncIter>
RefMut
which gives out&mut DynAsyncIter
; we just need to make it well-defined by using std pointer APIs.