rust-lang / rust-lang/impl-trait-utils
Design: Enable dynamic dispatch on traits with `async fn` and RPITIT
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 134
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Description
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
#[trait_variant(dyn)]
trait MyTrait {
type Item;
async fn foo(&self) -> Self::Item;
}
Instead of using a type like Box<dyn MyTrait> which wouldn't compile today, we would have the proc macro create a type DynMyTrait with this interface:
impl<'a, Item> DynMyTrait<'a, Item> {
fn new<T>(value: T) -> DynMyTrait<'a, Item>
where
T: MyTrait<Item = Item> + 'a,
Item: 'a,
{
...
}
}
impl<'a, Item> MyTrait for DynMyTrait<'a, Item> {
type Item = Item;
fn foo(&self) -> impl Future<Output = Self::Item> { /* return Pin<Box<dyn Future<Output = Item> here */ }
}
The struct itself would look something like this. A full explanation is in the links and in the exposition below.
struct DynMyTrait<'a, Item> {
ptr: *mut (dyn ErasedMyTrait<Item = Item> + 'a),
}
trait ErasedMyTrait {
type Item;
fn foo(&self) -> Pin<Box<dyn Future<Output = Self::Item>>>;
}
A full example including macro output is here: https://github.com/nikomatsakis/dyner/blob/main/src/async_iter.rs. Notes:
- The
ErasedAsyncItertrait 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 ErasedAsyncIteris a fat pointer. - Note the use of
RefandRefMutwrapper types (which would go in some support library) so that we can also haveDynAsyncIter::from_refandDynAsyncIter::from_ref_mut. These wrappers are slightly verbose, but due to their deref impls, can be reborrowed to create&DynMyTraitand&mut DynMyTraitrespectively. - 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 haveEDIT: Actually we probably do need something like this if we haveRef<DynAsyncIter>'s field beManuallyDrop<DynAsyncIter>RefMutwhich gives out&mut DynAsyncIter; we just need to make it well-defined by using std pointer APIs.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the async-fundamentals initiative explainer linked in the issue, then study the complete macro output in dyner's src/async_iter.rs example. Compare the proposed DynMyTrait, erased trait, ownership, and reference-wrapper design with this project's current proc-macro capabilities. Done means async fn and other RPITIT traits support dynamic dispatch while retaining static dispatch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100