Search code examples
rustassociated-typestrait-objects

How to make type-erased version of a trait with associated type?


Say there is a collection trait that has an associated type for its items:

trait CollectionItem {
    // ...
}

trait Collection {
    type Item: CollectionItem;
    
    fn get(&self, index: usize) -> Self::Item;
    // ...
}

Can I somehow type-erase this into a type that uses dynamic dispatch for both the Collection and the CollectionItem trait? i.e. wrap it into something like the following:

struct DynCollection(Box<dyn Collection<Item=Box<dyn CollectionItem>>>);
impl DynCollection {
  fn get(&self, index: usize) -> Box<dyn CollectionItem> {
    // ... what to do here?
  }
}
impl <C: Collection> From<C> for DynCollection {
  fn from(c: C) -> Self {
    // ... what to do here?
  }
}

Playground


Solution

  • You can add a private, type-erased helper trait:

    trait DynCollectionCore {
        fn get_dyn(&self, index: usize) -> Box<dyn CollectionItem>;
    }
    
    impl<C> DynCollectionCore for C
    where
        C: ?Sized + Collection,
        C::Item: 'static,
    {
        fn get_dyn(&self, index: usize) -> Box<dyn CollectionItem> {
            Box::new(self.get(index))
        }
    }
    

    Then use this to build a wrapper type:

    struct DynCollection(Box<dyn DynCollectionCore>);
    
    impl DynCollection {
        fn new<C>(inner: C) -> Self
        where
            C: Collection + 'static,
            C::Item: 'static,
        {
            Self(Box::new(inner))
        }
    }
    
    impl Collection for DynCollection {
        type Item = Box<dyn CollectionItem>;
    
        fn get(&self, index: usize) -> Box<dyn CollectionItem> {
            self.0.get_dyn(index)
        }
    }
    
    // note: something like this is also needed for `Box<dyn CollectionItem>:
    //       CollectionItem` to be satisfied
    impl<T: ?Sized + CollectionItem> CollectionItem for Box<T> {
        // ...
    }