eml*_*lai 9 generics compiler-errors traits rust
编译以下代码时:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我收到错误:
error: the trait `Drawable` is not implemented for the type `Drawable` [E0277]
src/main.rs:15 e.draw(&mut win);
^~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
试图告诉的错误消息是什么?另外,如何修复它?
有一个相关的问题,但解决方案是修改特征A(Drawable在我的情况下对应),但这是不可能的,因为Drawable来自外部库.
更新:将对象安全规则修复为1.0版本.也就是说,by-value self不再使方法对象不安全.
出现此错误是因为对象安全.
为了能够从特征中创建特征对象,特征必须是对象安全的.如果这两个语句都包含,则特征是对象安全的:
Sized要求,如trait Whatever: Sized {};如果这两个语句都为真,则方法是对象安全的:
where Self: Sized要求,如fn method() where Self: Sized;以下陈述均不成立:
Self除相关类型外,该方法以任何形式提及其签名,即使在引用下也是如此;如果您考虑更多这些限制,这些限制实际上是相当自然的.
请记住,当将值设置为特征对象时,将删除其类型的实际信息,包括其大小.因此,特征对象只能通过引用使用.当应用于特征对象时,引用(或其他智能指针,如Box或Rc)成为"胖指针" - 与指向值的指针一起,它们还包含指向该值的虚拟表的指针.
因为trait对象只能通过指针使用,self所以无法在它们上调用按值方法 - 您需要实际值才能调用此类方法.这在某一时刻违反了对象安全,这意味着使用这些方法的特征不能成为特征对象,但是,即使在1.0之前,规则已经被调整以允许self特征对象上的按值方法.但是,由于上述原因,仍然无法调用这些方法.有理由期望将来这个限制将被取消,因为它目前导致语言中的一些怪癖,例如,无法调用Box<FnOnce()>闭包.
Self 不能用于应该在trait对象上精确调用的方法,因为trait对象的实际类型已被删除,但为了调用这些方法,编译器需要知道这种擦除类型.
为什么不能在trait对象上调用静态方法,我猜,很明显 - 根据定义,静态方法"属于"trait本身,而不属于value,所以你需要知道实现trait的具体类型来调用它们.更具体地说,常规方法是通过存储在trait对象中的虚拟表来调度的,但是静态方法没有接收器,因此它们没有任何可以调度的内容,因此它们不能存储在虚拟表中.因此,在不知道具体类型的情况下,它们是不可调用的
我认为,不能通过其他原因调用通用特征方法,技术性比逻辑性更强.在Rust中,泛型函数和方法是通过单态化实现的 - 也就是说,对于具有一组具体类型参数的泛型函数的每个实例化,编译器生成一个单独的函数.对于语言用户来说,看起来他们正在调用泛型函数; 但是在每组类型参数的最低级别上,有一个单独的函数副本,专门用于实例化类型.
给定这种方法,为了在trait对象上调用泛型方法,你需要它的虚拟表包含几乎所有可能类型的泛型方法的每个可能实例化的指针,这自然是不可能的,因为它需要无限实例数.因此不允许在特征对象上调用泛型方法.
如果Drawable是外部特征,那么你就会陷入困境 - 不可能做你想要的事情,也就是说,要调用draw()异构集合中的每个项目.如果您的可绘制集是静态已知的,则可以为每个可绘制类型创建单独的集合,或者创建自己的集合,enum其中包含您拥有的每个可绘制类型的变体.然后你可以实现Drawableenum本身,这将是相当简单的.