让我们看看下面的代码:
trait Animal {
fn talk(&self);
}
struct Cat {}
struct Dog {}
impl Animal for Cat {
fn talk(&self) {
println!(“meow”);
}
}
impl Animal for Dog {
fn talk(&self) {
println!(“bark”);
}
}
// dyn method
fn animal_talk(a: &dyn Animal) {
a.talk();
}
fn main() {
let d = Dog {};
let c = Cat {};
animal_talk(&d);
animal_talk(&c);
}
可以看到 animal_talk()
对 cat and dog
都被调用了。**dyn**
告诉编译器不用确定确切的类型,只需要满足于对某个实现动物trait的类型的引用。这被称为动态派发,类型在运行时被确定,所以有一个运行时的开销。
但为什么在dyn之前有一个&? "指向一个特性的指针?" 没错! 因为类型(以及具体类型的大小)在编译时是不知道的;仅仅使用以下是非法的:
fn animal_talk(a: dyn Animal) {
a.talk();
}
编译器会报错:大小不详。 Rust 称其为 trait对象(&dyn Animal)。它表示一个指向具体类型的指针和一个指向函数指针的 vtable pointer
。Box<dyn Animal>
, Rc<dyn Animal>
也是 trait对象。它们也包含一个指向在堆上分配的具体类型的指针,该类型满足给定的 trait。 现在,我们看一下下面的代码:
fn animal_talk(a: impl Animal) {
a.talk();
}
fn main() {
let c = Cat{};
let d = Dog{};
animal_talk(c);
animal_talk(d);
}
上面代码的编译结果是OK,而且这里没有& 。 impl
使编译器在编译时确定类型,这意味着编译器将做一些函数名称处理,并将有两个函数的变体:一个是 dog
,另一个是 mat
。这被称为:单态化,不会有任何运行时的开销,但会导致代码膨胀 (相同的代码被多次实现)。 考虑一下下面的代码,这是否会被编译?
fn animal () -> impl Animal {
if (is_dog_available()) {
return Dog {};
}
Cat {}
}
编译失败了! 因为,这里的类型是在编译时确定的(静态派发)。这个错误不言而喻:
error[E0308]: mismatched types
|
| fn animal() -> impl Animal {
| ----------- expected because this return type...
| if (is_dog_available()) {
| return Dog {};
| ------ ...is found to be `Dog` here
| }
|
...
| Cat {}
| ^^^^^^ expected struct `Dog`, found struct `Cat`
|
= note: expected type `Dog`
found type `Cat`
编译器在这里首先将 Dog
作为返回类型归零,并期望在函数体中的其他返回类型也是同样类型。 那下面的代码呢?
fn animal() -> Box<dyn Animal> {
if (is_dog_available()) {
return Box::new(Dog {});
}
Box::new(Cat {})
}
现在编译结果是OK的,因为它不需要知道确切的返回类型(动态调度)。它所需要的只是一些满足给定特征的 Box
类型(Trait Object)。
对象安全
如果一个trait在它的方法中,实现它的类型作为一个值(而不是作为一个引用)被认为是不安全的对象。 考虑一下下面的代码:
trait Animal {
fn nop(self) {}
}
struct Cat {}
impl Animal for Cat {}
fn main() {
let c = Cat {};
let ct:&dyn Animal = &c;
c.nop();
}
会有以下错误:
error[E0277]: the size for values of type `Self` cannot be known at compilation time
|
| fn nop(self) {}
| ^^^^ - help: consider further restricting `Self`: `where Self: std::marker::Sized`
| |
| doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Self`
nop()
接收传值。但由于这是动态派发,类型和它的大小是不知道的。即使我们给 Self
加上 Sized
约束,即 fn nop(self) where Self:Sized {}
,编译器仍然会报错,因为在编译时没有办法知道大小,而且还会在代码中引入对 Self
的依赖(即把它当做一个值传入)。