理:编译器为每一个被泛型类型参数代替的具体类型生成了非泛型的函数和方法实现。单态化所产生的
代码进行 静态分发(static dispatch)。静态分发发生于编译器在编译时就知晓调用了什么方法的时候。
这与 动态分发(dynamic dispatch)相对,这时编译器在编译时无法知晓调用了什么方法。在动态分发
的情况下,编译器会生成在运行时确定调用了什么方法的代码。
当使用 trait 对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于 trait 对象代码的类型,所
以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用 trait 对象中的指针来知晓
需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。尽管在
编写示例 17-5 和可以支持示例 17-9 中的代码的过程中确实获得了额外的灵活性,但仍然需要权衡取舍。
trait 对象需要类型安全
只有对象安全(object-safe)的 trait 可以实现为特征对象。这里有一些复杂的规则来实现 trait 的对象
安全,但在实践中,只有两个相关的规则。如果一个 trait 中定义的所有方法都符合以下规则,则该 trait
是对象安全的:
• 返回值不是 Self
• 没有泛型类型的参数
Self 关键字是我们在 trait 与方法上的实现的别称,trait 对象必须是对象安全的,因为一旦使用 trait 对
象,Rust 将不再知晓该实现的返回类型。如果一个 trait 的方法返回了一个 Self 类型,但是该 trait 对象
忘记了 Self 的确切类型,那么该方法将不能使用原本的类型。当 trait 使用具体类型填充的泛型类型时
也一样:具体类型成为实现 trait 的对象的一部分,当使用 trait 对象却忘了类型是什么时,无法知道应
该用什么类型来填充泛型类型。
一个非对象安全的 trait 例子是标准库中的 Clone trait。Clone trait 中的 clone 方法的声明如下:
pub trait Clone {
fn clone(&self) -> Self;
}
String 类型实现了 Clone trait,当我们在 String 的实例对象上调用 clone 方法时,我们会得到一个
String 类型实例对象。相似地,如果我们调用 Vec<T> 实例对象上的 clone 方法,我们会得到一个
Vec<T> 类型的实例对象。clone 方法的标签需要知道哪个类型是 Self 类型,因为 Self 是它的返回类型。
当我们尝试编译一些违反 trait 对象的对象安全规则的代码时,我们会收到编译器的提示。例如,我们想
实现 17-4 的 Screen 结构体来保存一个实现了 Clone trait 而不是 Draw trait 的类型,如下所示
pub struct Screen {
pub components: Vec<Box<dyn Clone>>,
}
我们将会收到如下错误:
$ cargo build
Compiling gui v0.1.0 (file:///projects/gui)
error[E0038]: the trait `Clone` cannot be made into an object
--> src/lib.rs:2:29
|
2 | pub components: Vec<Box<dyn Clone>>,
| ^^^^^^^^^ `Clone` cannot be made into an object
|
= note: the trait cannot be made into an object because it requires `Self: Sized`
= note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
For more information about this error, try `rustc --explain E0038`.
error: could not compile `gui` due to previous error
这个错误意味着我们不能将此 trait 用于 trait 对象。