## 为使用不同类型的值而设计的 trait 对象 > [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch17-02-trait-objects.md) >
> commit 67876e3ef5323ce9d394f3ea6b08cb3d173d9ba9 在第八章,我们谈到了 vector 只能存储同种类型元素的局限。在列表 8-1 中有一个例子,其中定义了存放包含整型、浮点型和文本型成员的枚举类型`SpreadsheetCell`,这样就可以在每一个单元格储存不同类型的数据,并使得 vector 仍然代表一行单元格。当编译时就知道类型集合全部元素的情况下,这种方案是可行的。 有时,我们需要可扩展的类型集合,能够被库的用户扩展。比如很多图形化接口工具有一个条目列表,迭代该列表并调用每个条目的 draw 方法。我们将创建一个库 crate,包含称为 `rust_gui` 的 GUI 库。库中有一些为用户准备的类型,比如 `Button` 或 `TextField`,`rust_gui`的用户还会创建更多,有的用户会增加`Image`,有的用户会增加`SelectBox`,然后用它们在屏幕上绘图。我们不会在本章节实现一个完善的GUI库,只是展示如何把各部分组合起来。 当写 `rust_gui` 库时,我们不知道其他程序员需要什么类型,所以无法定义一个 `enum` 来包含所有的类型。然而 `rust_gui` 需要跟踪所有这些不同类型的值,需要有在每个值上调用 `draw` 方法能力。我们的 GUI 库不需要确切地知道调用 `draw` 方法会发生什么,只需要有可用的方法供我们调用。 在可以继承的语言里,我们会定义一个名为 `Component` 的类,该类上有一个`draw`方法。其他的类比如`Button`、`Image`和`SelectBox`会从`Component`继承并拥有`draw`方法。它们各自覆写`draw`方法以自定义行为,但是框架会把所有的类型当作是`Component`的实例,并在其上调用`draw`。 ### 定义一个带有自定义行为的Trait 不过,在Rust语言中,我们可以定义一个 `Draw` trait,包含名为 `draw` 的方法。我们定义一个由*trait对象*组成的vector,绑定了某种指针的trait,比如`&`引用或者一个`Box`智能指针。 之前提到,我们不会称结构体和枚举为对象,以区分其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和`impl`块中的行为是分开的,而其他语言则是数据和行为被组合到一个对象里。Trait 对象更像其他语言的对象,因为他们将其指针指向的具体对象作为数据,将在trait 中定义的方法作为行为,组合在了一起。但是,trait 对象和其他语言是不同的,我们不能向一个 trait 对象增加数据。trait 对象不像其他语言那样有用:它们的目的是允许从公有行为上抽象。 trait 对象定义了给定情况下应有的行为。当需要具有某种特性的不确定具体类型时,我们可以把 trait 对象当作 trait 使用。Rust 的类型系统会保证我们为 trait 对象带入的任何值会实现 trait 的方法。我们不需要在编译阶段知道所有可能的类型,却可以把所有的实例统一对待。Listing 17-03展示了如何定义一个名为`Draw`的带有`draw`方法的trait。 Filename: src/lib.rs ```rust pub trait Draw { fn draw(&self); } ``` Listing 17-3:`Draw` trait的定义 因为我们已经在第10章讨论过如何定义 trait,你可能比较熟悉。下面是新的定义:Listing 17-4有一个名为 `Screen` 的结构体,里面有一个名为 `components` 的 vector,`components` 的类型是Box。`Box` 是一个 trait 对象:它是 `Box` 内部任意一个实现了 `Draw` trait 的类型的替身。 Filename: src/lib.rs ```rust # pub trait Draw { # fn draw(&self); # } # pub struct Screen { pub components: Vec>, } ``` Listing 17-4: 定义一个 `Screen` 结构体,带有一个含有实现了 `Draw` trait 的 `components` vector 成员 在 `Screen` 结构体上,我们将要定义一个 `run` 方法,该方法会在它的 `components` 上调用 `draw` 方法,如Listing 17-5所示: Filename: src/lib.rs ```rust # pub trait Draw { # fn draw(&self); # } # # pub struct Screen { # pub components: Vec>, # } # impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } ``` Listing 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个组件上调用 `draw` 方法 这与带 trait 约束的泛型结构体不同(trait 约束泛型参数)。泛型参数一次只能被一个具体类型替代,而 trait 对象可以在运行时允许多种具体类型填充 trait 对象。比如,我们已经定义了 `Screen` 结构体使用泛型和一个 trait 约束,如Listing 17-6所示: Filename: src/lib.rs ```rust # pub trait Draw { # fn draw(&self); # } # pub struct Screen { pub components: Vec, } impl Screen where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } ``` Listing 17-6: 一种 `Screen` 结构体的替代实现,它的 `run` 方法使用通用类型和 trait 绑定 这个例子中,`Screen` 实例所有组件类型必需全是 `Button`,或者全是 `TextField`。如果你的组件集合是单一类型的,那么可以优先使用泛型和 trait 约束,因为其使用的具体类型在编译阶段即可确定。 而 `Screen` 结构体内部的 `Vec>` trait 对象列表,则可以同时包含 `Box