0
点赞
收藏
分享

微信扫一扫

rust 定义通用行为的 trait

为了实现 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>。让我们看看它是如何工作的,接着会讲到其运行时性能影响。

举报

相关推荐

0 条评论