简体   繁体   English

未为类型`x`实现特征'x`

[英]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. 有一个相关的问题,但是解决方案是修改特征A (在我的情况下,它对应于Drawable ),但这在这里是不可能的,因为Drawable来自外部库。

Update: fixed object safety rules to the 1.0 version of them. 更新:将对象安全规则固定为它们的1.0版本。 Namely, by-value self makes method object-unsafe no longer. 即,按值self不再使方法成为对象不安全的。

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 {} ; 它没有Sized要求,如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 ; 它在where Self: Sized具有where Self: Sized要求,如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; 此方法以任何形式(即使在引用下)在其签名中提及Self ,但关联类型除外;
    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. 当引用(或其他智能指针,如BoxRc )应用于特征对象时,它们将成为“胖指针”-与指向该值的指针一样,它们也包含指向该值的虚拟表的指针。

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. 由于特征对象只能通过指针使用,因此不能在其上调用按值self方法-您需要实际值才能调用此类方法。 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. 这在某一点上违反了对象安全性,这意味着无法将使用此类方法的特征作为特征对象,但是,即使在1.0之前,也已对规则进行了调整,以允许对特征对象使用按值self方法。 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. 有理由期望将来会取消此限制,因为它当前会导致某些语言上的怪癖,例如,无法调用Box<FnOnce()>闭包。

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. 不能在应该在特征对象上调用的方法中使用Self正是因为特征对象已删除了它们的实际类型,但要调用此类方法,编译器需要知道此擦除类型。

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. 更具体地说,常规方法是通过存储在trait对象中的虚拟表调度的,但是静态方法没有接收器,因此它们没有要调度的内容,因此,它们不能存储在虚拟表中。 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. 在Rust中,泛型函数和方法是通过单态化实现的-也就是说,对于带有具体类型参数集的泛型函数的每个实例化,编译器都会生成一个单独的函数。 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. 给定这种方法,为了在trait对象上调用泛型方法,您将需要其虚拟表包含指向所有可能类型的泛型方法的几乎每个实例的指针,这自然是不可能的,因为它将需要无限的实例化数。 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. 如果Drawable是一个外部特征,那么您就被困住了-不可能做您想做的事情,也就是说,不能对异构集合中的每个项目调用draw() 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. 如果您的一组可绘制对象是静态已知的,则可以为每种可绘制类型创建一个单独的集合,或者创建自己的enum ,该enum将包含您所拥有的每种可绘制类型的一个变体。 Then you can implement Drawable for the enum itself, which would be fairly straightforward. 然后,您可以为枚举本身实现Drawable ,这将非常简单。

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. 我指的是弗拉基米尔(Vladimir)的出色答案,它解释了对象的安全性,但是我担心的是,在讨论过程中,当前的具体问题被遗忘了。

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; 正如弗拉基米尔(Vladimir)所提到的那样,问题在于,泛型而不是类型的方法(泛型是整个生命周期的方法都是可以的)使它所属于的特征无法用于运行时多态。 this, in Rust, is called Object Safety. 在Rust中,这称为“对象安全”。

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 , ...). 后者要求RenderTarget必须是对象安全的,因为它现在已在运行时多态情况下使用(因此,没有静态方法,没有泛型方法,没有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). 另一个(更具技术性的)区别是,前者在编译时被“ monorphized”(即RT被实型替换并应用了所有相关的优化),而后者则没有(因此不会发生此类优化)。

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 在这种情况下,您不能这样做,但是如果给定的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. 似乎可以在上游进行这样的更改,因为您不能真正使用 RT大小的事实。

The other doesn't allow you to put arbitrary Drawable s in your Vec , but should work without allowing unsized types upstream. 另一个不允许您在Vec放置任意Drawable ,但是应该在不允许上游使用未调整大小的类型的情况下工作。 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; 可能需要添加From实现等。 you might find it easier if using wrapped_enum! 如果使用wrapped_enum!您可能会发现它更容易wrapped_enum! which will automatically implement those for you. 它将自动为您实现这些功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM