为了实现 gui 所期望的行为,让我们定义一个 Draw trait,其中包含名为 draw 的方法。接着可以定义一
个存放 trait 对象(trait object)的 vector。trait 对象指向一个实现了我们指定 trait 的类型的实例,以
及一个用于在运行时查找该类型的 trait 方法的表。我们通过指定某种指针来创建 trait 对象,例如 & 引
用或 Box<T> 智能指针,还有 dyn keyword,以及指定相关的 trait(第十九章 ”” 动态大小类型和 Sized
trait” 部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何
使用 trait 对象的位置,Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象
的 trait。如此便无需在编译时就知晓所有可能的类型。
之前提到过,Rust 刻意不将结构体与枚举称为 ” 对象”,以便与其他语言中的对象相区别。在结构体或枚
举中,结构体字段中的数据和 impl 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称
为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 则其更类似其他语言中的对象。
不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象
那么通用:其(trait 对象)具体的作用是允许对通用行为进行抽象。
示例 17-3 展示了如何定义一个带有 draw 方法的 trait Draw:
文件名: src∕lib.rs
pub trait Draw {
fn draw(&self);
}
示例 17-3:Draw trait 的定义
因为第十章已经讨论过如何定义 trait,其语法看起来应该比较眼熟。接下来就是新内容了:示例 17-4 定
义了一个存放了名叫 components 的 vector 的结构体 Screen。这个 vector 的类型是 Box<dyn Draw>,
此为一个 trait 对象:它是 Box 中任何实现了 Draw trait 的类型的替身。
# pub trait Draw {
# fn draw(&self);
# }
#
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
示例 17-4: 一个 Screen 结构体的定义,它带有一个字段 components,其包含实现了 Draw trait 的 trait
对象的 vector
在 Screen 结构体上,我们将定义一个 run 方法,该方法会对其 components 上的每一个组件调用 draw
方法,如示例 17-5 所示:
文件名: src∕lib.rs
# pub trait Draw {
# fn draw(&self);
# }
#
# pub struct Screen {
# pub components: Vec<Box<dyn Draw>>,
# }
#
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
示例 17-5:在 Screen 上实现一个 run 方法,该方法在每个 component 上调用 draw 方法
这与定义使用了带有 trait bound 的泛型类型参数的结构体不同。泛型类型参数一次只能替代一个具体
类型,而 trait 对象则允许在运行时替代多种具体类型。例如,可以定义 Screen 结构体来使用泛型和
trait bound,如示例 17-6 所示:
文件名: src∕lib.rs
# pub trait Draw {
# fn draw(&self);
# }
#
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}
impl<T> Screen<T>
where
T: Draw,
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
示例 17-6: 一种 Screen 结构体的替代实现,其 run 方法使用泛型和 trait bound
这限制了 Screen 实例必须拥有一个全是 Button 类型或者全是 TextField 类型的组件列表。如果只需要同
质(相同类型)集合,则倾向于使用泛型和 trait bound,因为其定义会在编译时采用具体类型进行单态化。
另一方面,通过使用 trait 对象的方法,一个 Screen 实例可以存放一个既能包含 Box<Button>,也能包
含 Box<TextField> 的 Vec<T>。让我们看看它是如何工作的,接着会讲到其运行时性能影响。