Trait `x`未被类型`x`实现。

9
在编译以下代码时:
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);
                         ^~~~~~~~~~~~~~

这个错误信息想要告诉我们什么?还有,如何修复它?
这里有一个相关问题,但那里的解决方案是修改特征A(在我的情况下对应于Drawable),但这里不可能这样做,因为Drawable来自外部库。
3个回答

6

更新: 将对象安全规则修正为它们的1.0版本。换句话说,按值传递的self不再使方法不安全。

这个错误是由于对象安全引起的。

为了能够从一个trait创建一个trait对象,该trait必须是对象安全的。如果同时满足以下两个条件,则trait是对象安全的:

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

如果同时满足以下两个条件,则方法是对象安全的:

  1. 它有where Self: Sized要求,例如fn method() where Self: Sized
  2. 以下任何一个条件都不成立:

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

如果你更深入地思考,这些限制实际上是相当自然的。

请记住,当值被转换为特质对象时,它们的类型信息(包括大小)会被抹去。因此,特质对象只能通过引用使用。当应用于特质对象时,引用(或其他智能指针,如BoxRc)变成了“胖指针”——除了指向值的指针外,它们还包含指向该值的虚表的指针。

由于特质对象只能通过指针使用,在它们身上不能调用按值传递的self方法——为了调用这样的方法,你需要实际的值。这曾经是一种违反对象安全性的行为,这意味着具有这样的方法的特质无法成为特质对象,但即使在1.0之前,规则已经被调整以允许特质对象上的按值传递的self方法。尽管如此,由于上述原因,仍然无法调用这些方法。有理由期望未来将取消这个限制,因为它目前导致语言中存在一些怪癖,例如无法调用Box<FnOnce()>闭包。

Self 不能在应该在特质对象上调用的方法中使用,因为特质对象具有其实际类型被擦除的特点,但是为了调用这些方法,编译器需要知道这个已擦除的类型。

我猜为什么不能在特质对象上调用静态方法,这是显而易见的 - 静态方法从定义上来说“属于”特质本身,而不是值,因此您需要知道实现特质的具体类型才能调用它们。更具体地说,常规方法通过存储在特质对象内部的虚拟表进行分派,但静态方法没有接收器,因此它们没有任何可分派的内容,因此它们无法存储在虚拟表中。因此,在不知道具体类型的情况下,它们是无法调用的。

泛型特质方法由于比较技术性的原因而无法调用,我认为这比逻辑更加复杂。在Rust中,通用函数和方法是通过单态化实现的 - 也就是说,对于每个具有一组具体类型参数的通用函数实例化,编译器都会生成一个单独的函数。对于语言用户来说,它看起来像是在调用通用函数; 但在最低层次上,对于每组类型参数,都有一个函数的副本,专门为实例化的类型工作。

考虑到这种方法,为了在一个特质对象上调用通用方法,您需要其虚拟表包含指向每个可能类型的通用方法的虚拟指针,这是不可能的,因为它将需要无限数量的实例化。因此,在特质对象上调用通用方法是被禁止的。
如果Drawable是外部特质,则无法做到您想要的,即在异构集合中调用每个项的draw()。如果您的可绘制集合是静态已知的,可以为每个可绘制类型创建单独的集合,或者创建自己的枚举,其中包含您拥有的每个可绘制类型的变体。然后,您可以为枚举本身实现Drawable,这将是相当简单的。

1
你需要让虚拟表包含指向通用方法的每一个可能实例的指针,这将需要无限数量的实例化。不,这并不是必须的——类型只有有限个,而且在代码中只有一小部分被使用。如果检测到这些类型,静态检测似乎是可行的,尽管代价可能不值得。 - Veedrac
3
“只有有限种类型”真的吗?实际上有无限多种类型,并且这里有一个证明。如果我们从以下命题出发:i32 是一种类型对于所有的T,Option<T> 都是一种类型(这些命题显然是正确的),那么可以轻松地推导出i32Option<i32>Option<Option<i32>>等所有类型。因此,类型集合是无限的,因为我们已经找到了它的无限子集。当然,程序中实际使用的类型集合是有限的,但是要查找并针对每个泛型特征生成方法,然后删除未使用的方法将非常困难。 - Vladimir Matveev
1
我还要补充一点,它似乎会在互操作性方面带来一些问题。假设在一个 crate 中定义了一个具有通用方法的 trait,并且在同一个 crate 中使用其 trait 对象。它需要为该 crate 生成某种虚拟表。现在,另一个 crate 使用了该 crate,并且也使用了相同 trait 的 trait 对象,但是对于其方法有不同的类型参数。现在我们有了两个不兼容的虚拟表,因此在第一个和第二个 crate 中创建的 trait 对象是不兼容的。如果开始使用共享库,情况似乎变得更加有趣。 - Vladimir Matveev
“只有有限数量的类型。”→ 是的。人类数量是无限的吗?你已经描述了可能类型的空间,但对于 Rust 来说,Option<Option<Option<i32>>> 直到你单态化它才算是一种类型。因此,找到所有类型就像在 Rust 用于缓存事物的任何表中查找一样简单。 - Veedrac
我还要补充的是,它似乎会在互操作性方面带来一些问题。确实,我同意这不太实用。 - Veedrac
显示剩余3条评论

3
我参考了Vladimir的优秀回答,解释了对象安全性,但我担心在讨论中具体的问题被遗忘了。
正如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)

另一个(更技术性的)区别是前者在编译时被“单态化”(即将RT替换为实际类型并应用所有相关的优化),而后者则不是这样的情况(因此,没有进行此类优化)。与此同时,后者需要RenderTarget在运行时多态使用,因此也需要它成为对象安全(也就是不能有静态方法、泛型方法、Self等)。


2
如果你被困在你所给的内容中,有两个选项可以尝试。
在这种情况下,你不能,但如果你得到的是一个未定大小的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 大小。另一个方法不允许您将任意的 Drawable 放入您的 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!,它会自动为你实现这些内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接