Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why must the associated type be specified in a collection of trait object references?

Here is an offending example (Playground):

// Some traits
trait Behaviour {
    type Sub: SubBehaviour;
}
trait SubBehaviour {}

// Some implementations of these traits
struct A;
impl Behaviour for A {
    type Sub = B;
}
struct B;
impl SubBehaviour for B {}

// Struct that holds a collection of these traits.
struct Example<'a> {
    behaviours: Vec<&'a dyn Behaviour>,
}

impl<'a> Example<'a> {
    fn add_behaviour<T: Behaviour>(&mut self, b: &'a T) {
        self.behaviours.push(b);
    }
}

fn main() {
    let b = A;
    let mut e = Example {
        behaviours: Vec::new(),
    };
    e.add_behaviour(&b);
}

I get:

error[E0191]: the value of the associated type `Sub` (from trait `Behaviour`) must be specified
  --> src/main.rs:17:29
   |
3  |     type Sub: SubBehaviour;
   |     ----------------------- `Sub` defined here
...
17 |     behaviours: Vec<&'a dyn Behaviour>,
   |                             ^^^^^^^^^ help: specify the associated type: `Behaviour<Sub = Type>`

Why must this type must be specified, particularly in this case where we are only storing a reference to the object? How can I get this code to work?

like image 806
Ross MacArthur Avatar asked Jun 18 '18 08:06

Ross MacArthur


2 Answers

All types must be statically known at compile time. If Rust would allow different associated types for elements of a Vec, type information could depend on indices which are only known at runtime.

I find it helpful to consider a smaller example:

trait Behaviour {
    type T;

    fn make_t(&self) -> T;
}

fn foo(my_vec: Vec<&dyn Behaviour>, index: usize) {
    let t = my_vec[index].make_t(); //Type of t depends on index
}

You were on the right track to fixing this though. I assume you introduced the SubBehaviour trait because you realized you need to put restrictions of what T can be. The thing is, in that case you don't need an associated type anymore.

trait SubBehaviour {}

trait Behaviour {
    fn make_t(&self) -> Box<dyn SubBehaviour>;

    fn ref_t(&self) -> &dyn SubBehaviour; // also fine
}

fn some_function(my_vec: Vec<&dyn Behaviour>, index: usize) {
    let t1 = my_vec[index].make_t();
}

The only limitation is that in your definition of Behaviour you can not do anything which would depend on the size of T, (like allocating it on the stack or moving it) since the size of T can not be specified by the SubBehaviour trait.

like image 151
Markus Klein Avatar answered Oct 30 '22 07:10

Markus Klein


So the answer to your first question is covered by Tim's answer and is correct, you might not want your Example to be generic. In that case, you need to use some sort of type erasure:

// Some traits
trait Behaviour {
    type Sub: SubBehaviour;
}
trait SubBehaviour {}

// Some implementations of these traits
struct A;
impl Behaviour for A {
    type Sub = B;
}

struct B;
impl SubBehaviour for B {}

struct AnyBehaviour {
    closure: Box<Fn()>,
}
impl AnyBehaviour {
    fn new<U: SubBehaviour, T: Behaviour<Sub = U>>(b: &T) -> Self {
        let closure = || {
            //let sub = T::Sub::new();
            println!("Can use T here");
        };

        AnyBehaviour {
            closure: Box::new(closure),
        }
    }
}

// Struct that holds a collection of these traits.
struct Example {
    behaviours: Vec<AnyBehaviour>,
}

impl Example {
    fn add_behaviour<U: SubBehaviour, T: Behaviour<Sub = U>>(&mut self, b: &T) {
        self.behaviours.push(AnyBehaviour::new(b));
    }
}

fn main() {
    let b = A;
    let mut e = Example {
        behaviours: Vec::new(),
    };
    e.add_behaviour(&b);
}

Within the closure, you have access to all the types needed call the traits functions with whatever subtype needed.

Why this happens, is mostly because you actually need a definition of the associated type in order for the trait to be "complete" so the compiler can work with it. Tim's answer answers that by the definition to be higher up in the chain (outside of Example) instead of inside.

like image 29
Seivan Avatar answered Oct 30 '22 07:10

Seivan