【问题标题】:Trait `x` is not implemented for the type `x`没有为类型“x”实现特征“x”
【发布时间】:2015-11-11 21:37:19
【问题描述】:

编译以下代码时:

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);
    }
}

我得到错误:

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

试图说明的错误信息是什么?还有,怎么解决?

有一个related question,但解决方案是修改特征A(在我的例子中对应于Drawable),但这里不可能,因为Drawable来自外部库。

【问题讨论】:

    标签: generics compiler-errors rust traits


    【解决方案1】:

    更新: 将对象安全规则修复为 1.0 版本。即,按值self 使方法对象不再安全。

    由于object safety而发生此错误。

    为了能够从特征中创建特征对象,特征必须是对象安全的。如果这两个语句都成立,则特征是对象安全的:

    1. 它没有Sized 要求,如trait Whatever: Sized {}
    2. 它的所有方法都是对象安全的。

    如果这两个陈述都为真,则方法是对象安全的:

    1. 它具有where Self: Sized 要求,如fn method() where Self: Sized
    2. 以下陈述均不成立:

      1. 此方法在其签名中以任何形式提及Self,即使在引用下,关联类型除外;
      2. 这个方法是静态的;
      3. 这个方法是通用的。

    如果你想得更多,这些限制实际上是相当自然的。

    请记住,当值被制成特征对象时,它们类型的实际信息会被删除,包括它们的大小。因此,trait 对象只能通过引用来使用。引用(或其他智能指针,如 BoxRc)在应用于 trait 对象时,成为“胖指针”——与指向值的指针一起,它们还包含指向该值的虚拟表的指针。

    因为 trait 对象只能通过指针使用,所以不能对它们调用按值 self 方法 - 您需要实际值才能调用此类方法。这在某一点上违反了对象安全,这意味着不能将具有此类方法的特征作为特征对象,但是,即使在 1.0 之前,规则已经被调整为允许特征对象上的按值 self 方法。但是,由于上述原因,这些方法仍然无法调用。有理由预计,未来这一限制将被取消,因为它目前会导致该语言出现一些怪癖,例如,无法调用 Box&lt;FnOnce()&gt; 闭包。

    Self 不能用于应该在 trait 对象上调用的方法中,因为 trait 对象的实际类型已被擦除,但为了调用此类方法,编译器需要知道这个已擦除的类型。

    我猜为什么不能在 trait 对象上调用静态方法是显而易见的 - 根据定义,静态方法“属于” trait 本身,而不是值,因此您需要知道实现 trait 的具体类型给他们打电话。更具体地说,常规方法是通过存储在 trait 对象中的虚拟表来分派的,但静态方法没有接收者,因此它们没有可分派的对象,因此它们不能存储在虚拟表中。因此,在不知道具体类型的情况下,它们是不可调用的。

    我认为,不能出于其他原因调用通用特征方法,技术性多于逻辑性。在 Rust 中,泛型函数和方法是通过单态化实现的——也就是说,对于具有一组具体类型参数的泛型函数的每个实例化,编译器都会生成一个单独的函数。对于语言用户来说,他们看起来像是在调用通用函数;但在每组类型参数的最低级别上,都有一个单独的函数副本,专门用于实例化类型。

    鉴于这种方法,为了在 trait 对象上调用泛型方法,您需要它的虚拟表包含指向所有可能类型的泛型方法的每个可能的实例化的指针,这自然是不可能的,因为它需要无限数量的实例化。因此,不允许在 trait 对象上调用泛型方法。

    如果Drawable 是一个外部特征,那么你就被卡住了——不可能做你想做的事,也就是说,对异构集合中的每个项目调用draw()。如果您的可绘制对象集是静态已知的,您可以为每个可绘制类型创建一个单独的集合,或者创建自己的enum,其中包含您拥有的每个可绘制类型的变体。然后您可以为枚举本身实现Drawable,这将相当简单。

    【讨论】:

    • “你需要它的虚拟表包含指向所有可能类型的泛型方法的几乎每一个可能的实例化的指针,这 [...] 将需要无限数量的实例化。”不,它不会 - 只有有限数量的类型,并且只有一小部分实际在代码中使用。如果不值得花钱的话,似乎可以静态检测这些。
    • “只有有限数量的类型”真的吗?有无数种类型,这里有一个证明。如果你从这些命题开始:i32 in Typesfor all T. Option&lt;T&gt; in Types(这显然是正确的),你可以很容易地推导出i32Option&lt;i32&gt;Option&lt;Option&lt;i32&gt;&gt;等等都是类型。因此类型集是无限的,因为我们刚刚找到了它的无限子集。当然,程序中实际使用的类型集是有限的,但是找到它并为它们的每个通用特征生成方法然后删除未使用的类型确实非常困难。
    • 我还要补充一点,它看起来给互操作性带来了一些问题。假设在一个 crate 中定义了具有泛型方法的 trait,并且它的 trait 对象在同一个 crate 中使用。它需要为这个 crate 生成某种虚拟表。现在这个 crate 被另一个 crate 使用,它也使用 trait 对象来表示相同的 trait,但其方法具有不同的类型参数。现在我们有两个不兼容的虚拟表,因此在第一个和第二个 crate 中创建的 trait 对象不兼容。如果我们开始使用共享库,事情似乎会变得更有趣。
    • “只有有限数量的类型”→ 是的。有无限数量的人类吗?您已经描述了可能类型的空间,但对于 Rust 而言,Option&lt;Option&lt;Option&lt;i32&gt;&gt;&gt; 不是类型,除非您将其单态化。因此,查找所有类型就像查看 Rust 用于缓存内容的任何表一样简单。
    • “我还要补充一点,它看起来给互操作性带来了一些问题。”确实,我同意这不切实际。
    【解决方案2】:

    我参考了 Vladimir 的出色回答,它解释了 Object 的安全性,但我担心在讨论过程中手头的具体问题被遗忘了。

    正如 Vladimir 所提到的,问题在于泛型方法优于类型(泛型优于生命周期很好)导致它所属的特征无法用于运行时多态性;这在 Rust 中称为对象安全。

    因此,最简单的解决方法是删除方法的泛型参数!

    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);
        }
    }
    

    主要区别:

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

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

    是后者要求RenderTarget 也是对象安全的,因为它现在用于运行时多态性情况(因此,没有静态方法,没有泛型方法,没有Self,...)。

    另一个(更技术性的)区别是前者在编译时是“单一化”的(即RT 被替换为真实类型并应用了所有相关优化)而后者不是(因此,没有这样的发生优化)。

    【讨论】:

      【解决方案3】:

      如果您无法接受所提供的内容,您可以尝试两种选择。

      在这种情况下,您不能,但如果给您一个未调整大小的RenderTarget

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

      你可以实现

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

      将您获得的类型重定向到对象安全的动态分派替代方案。看起来可以在上游进行这样的更改,因为您不能真正使用 RT 的大小这一事实。

      另一个不允许您将任意Drawables 放入您的Vec,但应该在不允许上游没有大小的类型的情况下工作。这是使用枚举来包装向量的可能值:

      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),
              }
          }
      }
      

      可能想要添加From 实现等;如果使用wrapped_enum! 会自动为您实现这些,您可能会发现会更容易。

      【讨论】:

        猜你喜欢
        • 2019-12-08
        • 2015-07-15
        • 2015-10-08
        • 1970-01-01
        • 1970-01-01
        • 2022-11-07
        • 1970-01-01
        • 2022-11-25
        • 1970-01-01
        相关资源
        最近更新 更多