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:
- it does not have
Sized
requirement, as in trait Whatever: Sized {}
;
- all its methods are object-safe.
A method is object-safe if both of these statements are true:
- it has
where Self: Sized
requirement, as in fn method() where Self: Sized
;
none of the following statements holds:
- this method mentions
Self
in their signature in any form, even under a reference, except associated types;
- this method is static;
- 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.