rust-lang / impl-trait-utils

Utilities for working with impl traits in Rust.
Apache License 2.0
89 stars 9 forks source link

Design: Enable dynamic dispatch on traits with `async fn` and RPITIT #34

Open tmandry opened 4 months ago

tmandry commented 4 months ago

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: