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

前端 未结 3 1408
我在风中等你
我在风中等你 2021-01-17 21:21

When compiling the following code:

trait RenderTarget {}

struct RenderWindow;
impl RenderTarget for RenderWindow {}

trait Drawable {
    fn draw

        
相关标签:
3条回答
  • 2021-01-17 21:42

    If you're stuck with what you're given, there are two options you could try.

    In this case, you can't, but if you were given an unsized RenderTarget

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

    you could implement

    trait DrawableDynamic {
        fn draw(&self, target: &mut RenderTarget);
    }
    
    impl<T: Drawable> DrawableDynamic for T {
        fn draw(&self, target: &mut RenderTarget) {
            Drawable::draw(self, target)
        }
    }
    

    to redirect the types you're given to an object-safe dynamically dispatched alternative. It looks like such a change could be made upstream, since you can't really use the fact that RT is sized.

    The other doesn't allow you to put arbitrary Drawables in your Vec, but should work without allowing unsized types upstream. This is to use an enum to wrap the possible values of the vector:

    enum AllDrawable {
        Square(Square),
        Triangle(Triangle)
    }
    
    impl Drawable for AllDrawable {
        fn draw<RT: RenderTarget>(&self, target: &mut RT) {
            match *self { 
                AllDrawable::Square(ref x) => x.draw(target),
                AllDrawable::Triangle(ref x) => x.draw(target),
            }
        }
    }
    

    One might want to add From implementations and such; you might find it easier if using wrapped_enum! which will automatically implement those for you.

    0 讨论(0)
  • 2021-01-17 21:43

    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).

    0 讨论(0)
  • 2021-01-17 21:46

    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.

    0 讨论(0)
提交回复
热议问题