Trait as generic argument

This conversation has appeared a couple times on this forum in various fashions, but all of the discussions I’ve seen have blended together concepts, or missed the points that I care about. I want to be able to write generic definitions which take a trait as a type argument. Specifically, I’d like to be able to write:

struct Object<T: trait> {
    backing: Box<dyn T>
}

impl<T: trait> Deref for Object<T> {
    type Target = dyn T;
    fn deref(&self) -> &dyn T {
        &*self.backing
    }
}

If you can’t tell from my example, my interest in this feature is mostly in making it easier to leverage dynamic dispatch in generic code by enabling us to write code which is generic over the trait being dispatched on, rather than just code generic over the type which implements the trait.

My questions are:

  1. Is there a way to express something semantically equivalent in Rust as it stands today?

  2. If not, what is preventing the Rust compiler from understanding this kind of code?

  3. What syntax would most clearly communicate this pattern?

2 Likes

I believe you want what’s commonly referred to as “Higher-Kinded Types”, or HKTs for short. This a form of genericity where types, functions, etc. are not parametrized by a single type, but by a family of types.

We don’t have these today, and for complicated reasons revolving around type inference, the general consensus so far has been that we probably don’t want to have them in Rust for now. Instead, the short-term plan is to add so-called “Generic Associated Types” (aka GATs), which can be summarized as making the following kind of code legal:

trait MyTrait {
    type MyTypeFamily<T>;
}

There is an accepted RFC for GATs, but some ongoing compiler refactorings must be completed before they can be released to the world.

Many HKT patterns can be translated to a GAT-based form, though some code can be easier to express in one form or the other. I’m not sure if your example could be translated to a GAT-based form, though, because you want to be parametrized by a trait and not a generic type.

You can do something similar today

struct Object<T: ?Sized> { backing: Box<T> }

impl<T: ?Sized> Deref for Object<T> {
    type Target = T;
    fn deref(&self) -> &T { &*self.backing }
}

But there isn’t a way to force T to be a trait.

2 Likes

I don’t know much about GAT but this proposal seems to make ECS very nice to work with. It would be very nice to be able to just add an instance of some struct that already implements several traits and then pass that parent structure around (generically) to be used as “a reference” to that inner struct

1 Like

This is basically {-# LANGUAGE ConstraintKinds #-} in Haskell.

It’s on my long term agenda:

With respect to using the generic trait parameter T in dyn T, you have to somehow prove that T is object safe. This constraint could potentially be inferred from usage, but that is unlike how Rust typically does things. Thus I think you would need some sort of disambiguation struct Object<T: dyn trait> or struct Object<dyn trait T> do achieve this.

5 Likes