Drew
Drew

Reputation: 8963

Matching a generic parameter to an associated type in an impl

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.

Upvotes: 25

Views: 8288

Answers (3)

oli_obk
oli_obk

Reputation: 31223

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());
    }
}

Upvotes: 6

Shepmaster
Shepmaster

Reputation: 430961

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.

Upvotes: 16

swizard
swizard

Reputation: 2701

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());
    }
}

Upvotes: 28

Related Questions