Design patterns for traits & async?


I’m working on a system that’s making extensive use of futures and tokio. This system has various subsystems which have fairly narrow APIs, and which may have multiple implementations. In other words, a good match for traits.

Other design points:

  • a number of the methods return async results - ie, implement either Future or Stream
  • I’d like the traits to be usable as trait objects for when I need to either decouple the concrete type or have runtime dynamism
  • the APIs are complex enough to need a number of related types

As a semi-hypothetical example:

trait Index {
    type Error;

    type Key;
    type Value;
    type Entry: Entry<Index=Self>;

    fn put(&self, key: &Self::Key, val: Self::Value)
        -> impl Future<Item=(), Error=Self::Error>; // placeholder syntax
    fn get(&self, key: &Self::Key)
        -> impl Future<Item=Option<Self::Value>, Error=Self::Error>;
    fn entry(&self, key: &Self::Key)
        -> impl Future<Item=Self::Entry, Error=Self::Error>;
    // ...

trait Entry {
    type Index: Index;

    fn get(&self) -> EntryType<Self::Index>;
    // ...

enum EntryType<Index: Index> {
   // ...

This has several problems:

  • impl Trait doesn’t exist yet, and even if it did, it’s not going to work on trait methods.
  • To make up for lack of impl Trait, it requires that every method needs to have a corresponding associated type: type GetReturn: Future<Item=Self::Value, ...>; - which will get pretty cumbersome for traits that have more than a handful of methods.
  • The linked associated types that refer to Self make it non-object-safe

This last point prevents me from implementing something like:

struct BoxIndex<Idx: Index, K, V>(Idx, PhantomData<(K, V)>);

impl<Idx: Index, K, V> impl Index for BoxIndex<Idx, K, V> {
    type Key = K;
    type Value = V;
    type Entry = BoxEntry<Self>;

// ...

which boxes up all the specific implementations of Index to make them a uniform erased type (yet still having concrete types for K and V).

So two questions:

  • is this the best way to handle traits whose methods return traits?
  • is there a way I can box these things into type-erased trait objects?

Thoughts, suggestions, clues?