23

I have a trait with an associated type and a generic struct::

trait Generator {
    type Foo;
    fn generate(&self) -> Self::Foo;
}

struct Baz<A, B>
where
    A: Generator,
{
    generator: A, // will be some struct implementing Generator, but the exact type will vary
    vec: Vec<B>,  // Each element will be A::Foo
}

I want to generate and put it into my vector:

impl<A: Generator, B> Baz<A, B> {
    fn addFoo(&mut self) {
        self.vec.push(self.generator.generate());
    }
}

Uh-oh! Compile error:

error[E0308]: mismatched types
  --> src/main.rs:16:27
   |
16 |             self.vec.push(self.generator.generate());
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found associated type
   |
   = note: expected type `B`
              found type `<A as Generator>::Foo`

Fair enough, I must explain to the compiler that B is the same as A::Foo; let's try with where:

impl<A: Generator, B> Baz<A, B>
where
    A::Foo = B,
{

which doesn't help:

error: equality constraints are not yet supported in where clauses (#20041)
  --> src/main.rs:16:5
   |
16 |     A::Foo = B,
   |     ^^^^^^^^^^

Hmm, no equals. Maybe I can do this with the colon operator instead?

impl<A: Generator, B> Baz<A, B>
where
    B: A::Foo,
{
error[E0405]: cannot find trait `Foo` in `A`
  --> src/main.rs:16:11
   |
16 |     B: A::Foo,
   |           ^^^ not found in `A`

Nope, now it's complaining about A. Maybe I should say Generator?

impl<A: Generator, B> Baz<A, B>
where
    B: Generator::Foo,
{
error[E0404]: expected trait, found associated type `Generator::Foo`
  --> src/main.rs:16:8
   |
16 |     B: Generator::Foo,
   |        ^^^^^^^^^^^^^^ not a trait

Well good work, compiler — it's not a trait; it's an associated type, but that doesn't tell me how to write a where clause that matches it.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Drew
  • 8,675
  • 6
  • 43
  • 41

3 Answers3

27

I must explain to the compiler that B is the same as A::Foo

There is a special syntax for it:

impl<A, B> Baz<A, B>
where
    A: Generator<Foo = B>,
{
    fn add_foo(&mut self) {
        self.vec.push(self.generator.generate());
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
swizard
  • 2,551
  • 1
  • 18
  • 26
  • I think this is the answer to what he asks for, not what he wants. Thanks, I didn't know about this syntax. – oli_obk Mar 31 '15 at 08:02
15

The trick is to only have a single generic parameter:

trait Generator {
    type Foo;
    fn generate(&self) -> Self::Foo;
}

struct Baz<G>
where
    G: Generator,
{
    generator: G,
    vec: Vec<G::Foo>,
}

impl<G> Baz<G>
where
    G: Generator,
{
    fn add_foo(&mut self) {
        self.vec.push(self.generator.generate());
    }
}

Since the vector will contain G::Foo, we can actually just say that.

The Rust style is snake_case, so I updated that as well as made the type parameter G to help the reader.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
6

You could get rid of the generic argument B and instead of constraining B, directly pass A::Foo as the second generic argument to Baz, but I'm not sure if your actual problem matches the simplified example you showed.

impl<A: Generator> Baz<A, A::Foo> {
    fn addFoo(&mut self)  {
        self.vec.push(self.generator.generate());
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
oli_obk
  • 28,729
  • 6
  • 82
  • 98