Matching a generic parameter to an associated type

2019-01-25 05:44发布

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.

标签: generics rust
3条回答
SAY GOODBYE
2楼-- · 2019-01-25 06:20

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.

查看更多
姐就是有狂的资本
3楼-- · 2019-01-25 06:23

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());
    }
}
查看更多
做个烂人
4楼-- · 2019-01-25 06:25

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());
    }
}
查看更多
登录 后发表回答