Why is GenericB not implemented for Anon_b


#1

Hey guys, I have the following code:

pub trait A {
    fn method_a(&self) -> f64;
}

trait B<T> where T: A {
    fn method_b(&mut self) -> &Vec<T>;
}

trait GenericB {
    fn method_generic(&mut self) -> Vec<&A>;
}

impl<T> GenericB for B<T> where T: A {
    fn method_generic(&mut self) -> Vec<&A> {
        B::method_b(self).iter().map(
        |v| v as &A
        ).collect()
    }
}

struct Anon_a {
    pub x: f64
}

impl A for Anon_a {
    fn method_a(&self) -> f64 {
        self.x
    }
}

struct Anon_b {
    pub a: Option<Vec<Anon_a>>
}

impl B<Anon_a> for Anon_b {
    fn method_b(&mut self) -> &Vec<Anon_a> {
        self.a.as_ref().unwrap()
    }
}

fn main() {
    let b = Anon_b {
        a: Some(vec![Anon_a{x: 32.3}, Anon_a{x: 64.0}])
    };
    let c: Box<GenericB> = Box::new(b);
}

However, this does not compile with the error:

error[E0277]: the trait bound `Anon_b: GenericB` is not satisfied
  --> src/main.rs:45:28
   |
45 |     let c: Box<GenericB> = Box::new(b);
   |                            ^^^^^^^^^^^ the trait `GenericB` is not implemented for `Anon_b`
   |
   = note: required for the cast to the object type `GenericB`

Why does this happen given that I have impl<T> GenericB for B<T> where T: A and impl B<Anon_a> for Anon_b and also impl A for Anon_a, e.g. to me Anon_b should be indeed B<T> where T:A.

Also how do I fix it (after I know why it doesn’t work)?


#2

When a trait is used as a type, it refers to a “trait object”, i.e. dynamic dispatch. (For what it’s worth, this has been criticized as confusing and may be deprecated someday in favor of more explicit syntax.)

So this line:

impl<T> GenericB for B<T> where T: A

implements GenericB not for any type that implements the B<T> trait, but only for the trait object type also named B<T>.

I think you want something like this instead:

impl<T, U> GenericB for T where T: B<U>, U: A

edit: except that won’t work because the compiler wouldn’t know what to pick for U. You should probably use an associated type instead.


#3

Thanks, with associated types it does work indeed. I’m slightly confused though why we need associated types in the first place, as they are some what equivalent to generic parmeters, no?


#4

There are a couple of subtle differences.

Associated types are a little more restricted in scope than generic traits: you can only implement a trait once for a type, so each type will have at most one associated type for that trait. In contrast, with generic traits, the amount of instantiations for a given type is unbounded: a given type can implement both GenericTrait<T> and GenericTrait<U> with T != U. This is what allows traits like From<T> to work well.

There is a price to pay for this extra generality of generic traits, however, which is that they cause type parameter bloat in your code: you need to constantly redefine the type T that should be put inside of GenericTrait<T>, something which is unnecessary with associated types. Moreover, the compiler can sometimes have trouble figuring out which instance of the generic trait you are talking about, whereas this cannot happen with associated types because there is only one type that can go in that place.

As a result, my general philosophy is: use associated types if you can (i.e. they are good enough for modeling the problem domain), and generic traits if you must. Which is actually just a consequence of the “use the simplest abstraction that will fit the bill” philosophical razor.


#5

Thanks for that clarification. From what you’ve written I think my short interpretation is:

  1. If the intention is to have only a single implementation of the trait per concrete type - use associated types.
  2. If the intention is to have multiple implementations of the trait per concrete type - use generic types.
  3. If you are not sure, then if you think other people using the library may need 2 then do 2, if that is highly unlikely use 1.