Trait objects with associated types

@ogeon, thanks from me too. That's what I was looking for, but I couldn't word my request as well as @matthewhammer

One further question. Why can't these additional traits be implicit? It looks like they are really a straightforward boilerplate.

@eterevsky, I can imagine some instances where there's an obvious way to erase the associated type, e.g., when no functions' types in the trait mention it.

But in my example, there's a "non-trivial" conversion from trait T to trait S. In particular, I create a version of doit in trait S whose type does not mention the associated type t:

trait T : Debug {
    type t;
    fn get (self:&Self) -> Self::t ;
    fn doit (self:&Self, Self::t ) -> String ;
}

trait S : Debug {
    // type t is omitted from this trait
    fn doit (self:&Self) -> String ;
}

To do this conversion, the implementation of S for all T uses two functions from T: the doit function and the get function. It composes these two functions to create S's version of doit where T::t is forgotten:

impl <A, B: T<t=A>> S for B {
    fn doit (self:&Self) -> String {
        self.doit ( self.get () )
    }
}

I can't see how one would derive this code automatically, so I can understand why rustc doesn't try to do so. But maybe there are simpler cases where rustc could do this implicitly (like T::t is never mentioned in the types of functions for T to begin with) ?

On the other hand, it doesn't seem very burdensome to just add the appropriate trait that drops the associated type and the impl to make the conversion explicit.

I think this is a similar issue as with "object safety", where some traits can't be used as trait objects if they have generic methods. I can't say for sure, but it seems like it's a similar situation where they chose not to filter out only the non-generic methods.

Hi,

I like the pattern @ogeon suggested and how @matthewhammer implemented it. It's a very specific use-case, though. I'm wondering if there is a more flexible solution along the initial question:

I run into this with error types, for example. Consider the following:

trait Thing {
  type Error: Error;
}

struct SomeThing {
}

impl Thing for SomeThing { 
  type Error = SomeError;
}

I cannot easily pass a SomeThing as a Thing<Error=Error> even though SomeError implements Error. I somewhat get why it is that way, I'm just wondering if anybody has a nice solution to this.

That can be done via an intermediate type parameter E: Error. Then, you can pass in SomeThing as T: Thing<Error=E>. For example,

fn foo<E, T>(_v: T)
    where E: Error,
          T: Thing<Error=E>
{}

fn main() {
    foo(SomeThing);
}

Playground

Sure, but that only works for static dispatch. It doesn't work with for example a heterogeneous Vec<Thing<_>>. For that, I'm currently adding a dynamic dispatch wrapper trait DynamicThing that is implemented for all Thing<Error=E> and implements Thing<Error=Box<Error>>. Then, I can have a Vec<Box<DynamicThing>>. I find it difficult to implement, though, so I was asking whether there is a simpler solution.

For reference, here is my implementation:

use std::io::Error as IoError;
use std::error::Error;

trait Trait {
    type Error;
    fn do_it(&self);
}

struct DynamicThing<E: Error, T: Trait<Error = E>> {
    thing: T
}

impl<E: Error, T: Trait<Error = E>> Trait for DynamicThing<E, T> {
    type Error = Box<Error>;
    fn do_it(&self) {
        self.thing.do_it()
    }
}

impl<'a, E: Error, T: Trait<Error = E>> From<T> for DynamicThing<E, T> {
    fn from(thing: T) -> DynamicThing<E, T> {
        DynamicThing { thing: thing }
    }
}

struct Thing {

}

impl Trait for Thing {
    type Error = IoError;
    fn do_it(&self) {
        println!("Do one thing")
    }
}

struct ThingTwo {

}

impl Trait for ThingTwo {
    type Error = IoError;
    fn do_it(&self) {
        println!("Do another thing")
    }
}

fn main() {
    let thing_one = DynamicThing::from(Thing {});
    let thing_two = DynamicThing::from(ThingTwo {});
    let things: Vec<&Trait<Error = _>> = vec![&thing_one, &thing_two];
    for thing in things {
        thing.do_it();
    }
}

I switched to Box<Error> for most things in the meantime.