Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trait `x` is not implemented for the type `x`

When compiling the following code:

trait RenderTarget {}

struct RenderWindow;
impl RenderTarget for RenderWindow {}

trait Drawable {
    fn draw<RT: RenderTarget>(&self, target: &mut RT);
}

fn main() {
    let mut win = RenderWindow;
    let mut vec: Vec<Box<Drawable>> = Vec::new();

    for e in &vec {
        e.draw(&mut win);
    }
}

I get the error:

error: the trait `Drawable` is not implemented for the type `Drawable` [E0277]
src/main.rs:15         e.draw(&mut win);
                         ^~~~~~~~~~~~~~

What is the error message trying to tell? Also, how to fix it?

There's a related question but the solution there was to modify the trait A (which corresponds to Drawable in my case), but that's not possible here since Drawable is from an external library.

like image 685
emlai Avatar asked Aug 18 '15 19:08

emlai


2 Answers

Update: fixed object safety rules to the 1.0 version of them. Namely, by-value self makes method object-unsafe no longer.

This error happens because of object safety.

In order to be able to create a trait object out of a trait, the trait must be object-safe. A trait is object-safe if both of these statements hold:

  1. it does not have Sized requirement, as in trait Whatever: Sized {};
  2. all its methods are object-safe.

A method is object-safe if both of these statements are true:

  1. it has where Self: Sized requirement, as in fn method() where Self: Sized;
  2. none of the following statements holds:

    1. this method mentions Self in their signature in any form, even under a reference, except associated types;
    2. this method is static;
    3. this method is generic.

These restrictions are in fact fairly natural if you think more of them.

Remember that when values are made into trait objects, actual information of their type is erased, including their size. Therefore, trait objects can only be used through a reference. References (or other smart pointers, like Box or Rc), when applied to trait objects, become "fat pointers" - along with the pointer to the value, they also contain a pointer to the virtual table for that value.

Because trait objects can only be used through a pointer, by-value self methods can't be called on them - you'd need the actual value in order to call such methods. This was a violation of object safety at one point, which meant that traits with such methods couldn't be made trait objects, however, even before 1.0 the rules had been tweaked to allow by-value self methods on trait objects. These methods still can't be called, though, due to the reason described above. There are reasons to expect that in the future this restriction will be lifted because it currently leads to some quirks in the language, for example, the inability to call Box<FnOnce()> closures.

Self can't be used in methods which should be called on trait objects precisely because trait objects have their actual type erased, but in order to call such methods the compiler would need to know this erased type.

Why static methods can't be called on trait objects, I guess, is obvious - static methods by definition "belong" to the trait itself, not to the value, so you need to know the concrete type implementing the trait to call them. More concretely, regular methods are dispatched through a virtual table stored inside a trait object, but static methods do not have a receiver, so they have nothing to dispatch on, and for this reason they can't be stored in a virtual table. Thus they are uncallable without knowing the concrete type.

Generic trait methods can't be called for another reason, more technical than logical, I think. In Rust generic functions and methods are implemented through monomorphization - that is, for each instantiation of a generic function with a concrete set of type parameters the compiler generate a separate function. For the language user it looks like that they're calling a generic function; but on the lowest level for each set of type parameters there is a separate copy of the function, specialized to work for the instantiated types.

Given this approach, in order to call generic methods on a trait object you would need its virtual table to contain pointers to virtually each and every possible instantiation of the generic method for all possible types, which is, naturally, impossible because it would require infinite number of instantiations. And so calling generic methods on trait objects is disallowed.

If Drawable is an external trait, then you're stuck - it is impossible to do what you want, that is, to call draw() on each item in a heterogeneous collection. If your set of drawables is statically known, you can create a separate collection for each drawable type or, alternatively, create your own enum which would contain a variant for each drawable type you have. Then you can implement Drawable for the enum itself, which would be fairly straightforward.

like image 60
Vladimir Matveev Avatar answered Oct 07 '22 10:10

Vladimir Matveev


I refer to Vladimir's excellent answer which explains Object's safety, however I am afraid than in the middle of the discussion the concrete problem at hand was forgotten.

As Vladimir mentions, the issue is that a method generic over types (generic over lifetimes is fine) renders the trait it belongs to unusable for run-time polymorphism; this, in Rust, is called Object Safety.

The simplest fix, therefore, is to remove the generic parameter of the method!

trait RenderTarget {}

struct RenderWindow;
impl RenderTarget for RenderWindow {}

trait Drawable {
    fn draw(&self, target: &mut RenderTarget);
}

fn main() {
    let mut win = RenderWindow;
    let mut vec: Vec<Box<Drawable>> = Vec::new();

    for e in &vec {
        e.draw(&mut win);
    }
}

The main difference between:

fn draw<RT: RenderTarget>(&self, target: &mut RT)

and

fn draw(&self, target: &mut RenderTarget)

is that the latter requires RenderTarget to be Object Safe too as it is now used in a run-time polymorphism situation (so, no static method, no generic method, no Self, ...).

Another (more technical) difference is that the former is "monorphised" at compile-time (that is RT is substituted with the real type and all relevant optimizations applied) whereas the latter is not (and so, no such optimizations occur).

like image 43
Matthieu M. Avatar answered Oct 07 '22 10:10

Matthieu M.