编译以下代码时:
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
来自外部库。
答案 0 :(得分:6)
更新:修复了1.0版本的对象安全规则。也就是说,按值self
不再使方法对象不安全。
由于object safety而发生此错误。
为了能够从特征中创建特征对象,特征必须是对象安全的。如果这两个语句都成立,则特征是对象安全的:
Sized
的要求,如trait Whatever: Sized {}
; 如果这两个语句都为真,则该方法是对象安全的:
where Self: Sized
要求,如fn method() where Self: Sized
; 以下陈述均不成立:
Self
,即使在引用下,相关类型除外; 如果您考虑更多这些限制,这些限制实际上是相当自然的。
请记住,当将值设置为特征对象时,将删除其类型的实际信息,包括其大小。因此,特征对象只能通过引用使用。引用(或其他智能指针,如Box
或Rc
),当应用于特征对象时,变为“胖指针” - 与指向值的指针一起,它们还包含指向虚拟表的指针为了这个价值。
因为特征对象只能通过指针使用,所以无法在它们上调用按值self
方法 - 您需要实际值才能调用此类方法。这在某一时刻违反了对象安全性,这意味着使用此类方法的特征无法成为特征对象,但是,即使在1.0之前,规则已经过调整以允许特征对象上的按值self
方法。但是,由于上述原因,仍然无法调用这些方法。有理由期望将来可以解除此限制,因为它目前会导致语言出现一些怪癖,例如,无法调用Box<FnOnce()>
闭包。
Self
不能用于应该在trait对象上精确调用的方法,因为trait对象的实际类型已被删除,但为了调用这些方法,编译器需要知道这种擦除类型。 / p>
为什么无法在特征对象上调用静态方法,我猜,很明显 - 按定义静态方法“属于”特征本身,而不属于值,所以你需要知道实现特征的具体类型给他们打电话。更具体地说,常规方法是通过存储在trait对象中的虚拟表来调度的,但是静态方法没有接收器,因此它们没有任何可以调度的内容,因此它们不能存储在虚拟表中。因此,在不知道具体类型的情况下,它们是不可赎回的。
我认为,不能通过其他原因调用通用特征方法,技术性比逻辑性更强。在Rust中,泛型函数和方法是通过单态化实现的 - 也就是说,对于具有一组具体类型参数的泛型函数的每个实例化,编译器生成一个单独的函数。对于语言用户来说,看起来他们正在调用泛型函数;但是在每组类型参数的最低级别上,有一个单独的函数副本,专门用于实例化类型。
鉴于这种方法,为了在特征对象上调用泛型方法,您需要其虚拟表包含指向几乎所有可能类型的泛型方法的每个可能实例的指针,当然,这是不可能的,因为它需要无数次的实例化。因此,不允许在特征对象上调用泛型方法。
如果Drawable
是一个外部特征,那么你就会陷入困境 - 不可能做你想要的事情,也就是说,在异构集合中的每个项目上调用draw()
。如果您的drawables集是静态已知的,则可以为每个可绘制类型创建单独的集合,或者创建自己的enum
,其中包含您拥有的每个可绘制类型的变体。然后你可以为枚举本身实现Drawable
,这将非常简单。
答案 1 :(得分:3)
我指的是弗拉基米尔的优秀答案,它解释了对象的安全性,但是我担心在讨论过程中,手头的具体问题被遗忘了。
正如弗拉基米尔所提到的,问题是一个泛型类型的方法(泛命的生命周期很好)会使它所属的特性无法用于运行时多态性;在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
,.. )。
另一个(技术上的)差异是前者是&#34; monorphised&#34;在编译时(即RT
替换为实际类型并应用所有相关优化)而后者不是(因此,不会发生此类优化)。
答案 2 :(得分: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!
会自动为您实现这些功能,您可能会觉得更容易。