3

I write a lib like:

struct Foo<A: AppleTrait, B: BananaTrait, C: CarrotTrait> {...}

impl<A: AppleTrait, B: BananaTrait, C: CarrotTrait> Foo<A,B,C> {...}

struct Bar<A: AppleTrait, B: BananaTrait, C: CarrotTrait> {...}

impl<A: AppleTrait, B: BananaTrait, C: CarrotTrait> Bar<A,B,C> {...}

... and many more...

Notice that, things like <A: AppleTrait, B: BananaTrait, C: CarrotTrait> always come together, and has appeared many many times. So I want to eliminate such a burden.

Thus I hope I can do something like:

define_the_trait_alias ABCTrait = (A: AppleTrait, B: BananaTrait, C: CarrotTrait); // How to do this?

struct Foo<ABC: ABCTrait> {...}

impl<ABC: ABCTrait> Foo<ABC> {...}

Thanks!

ch271828n
  • 15,854
  • 5
  • 53
  • 88
  • You may be able to eliminate a lot of that redundancy by simply omitting the bounds in the places where they are not needed. See (particularly my answer to) [Should trait bounds be duplicated in struct and impl?](https://stackoverflow.com/q/49229332/3650362) – trent Apr 25 '21 at 12:59
  • @trentcl Thanks! But even if it is omitted, it is still kind of long :/ – ch271828n Apr 25 '21 at 13:01

2 Answers2

5

Perhaps you can use associated types rather than generics. For example:

trait AbcTrait {
    type Apple: AppleTrait;
    type Banana: BananaTrait;
    type Carrot: CarrotTrait;
}

struct Foo<Abc: AbcTrait> {
    abc: Abc,
}
impl<Abc: AbcTrait> Foo<Abc> {}

struct Bar<Abc: AbcTrait> {
    abc: Abc,
}
impl<Abc: AbcTrait> Bar<Abc> {}

When defining a concrete implementation of the trait, you get to choose which concrete type implements which associated type requested by the trait:

impl AbcTrait for Salad {
    type Apple = GrannySmith;
    type Banana = Cavendish;
    type Carrot = LunarWhite;
}
user4815162342
  • 141,790
  • 18
  • 296
  • 355
4

You can make a super-trait that takes A, B, and C as associated types. Then you implement that trait for (A,B,C) and use that whenever you need to specify types, e.g., some_func_that_takes_type_params::<(StructA, StructB, StructC)>(). To access A, B, or C, you use the associated types instead:

trait SuperTrait{
    type A: AppleTrait;
    type B: BananaTrait;
    type C: CarrotTrait;
}
impl<A: AppleTrait, B: BananaTrait, C: CarrotTrait> SuperTrait for (A,B,C)
{
    type A = A;
    type B = B;
    type C = C;
}

struct Foo<D: SuperTrait> (std::marker::PhantomData<D>);

impl<D: SuperTrait> Foo<D> {
    /// Example function that returns an A
    fn return_a(&self) -> D::A{
        todo!()
    }
}
Emoun
  • 2,297
  • 1
  • 13
  • 20