Rust 学习笔记:trait 对象

Rust 学习笔记:trait 对象

  • Rust 学习笔记:trait 对象
    • 定义共同行为的特征
    • 实现 trait
    • trait 对象执行动态分派

Rust 学习笔记:trait 对象

假设我们创建一个名为 gui 的库 crate。这个 crate 可能包括一些供人们使用的类型,比如 Button 或 TextField。此外,gui 用户希望创建自己的可绘制类型:例如,一个程序员可能会添加 Image,而另一个程序员可能会添加 SelectBox。在编写库时,我们不可能知道并定义其他程序员可能想要创建的所有类型。但是我们知道 gui 需要跟踪许多不同类型的值,它需要对这些不同类型的值调用一个 draw 方法。

要在具有继承的语言中做到这一点,我们可以定义一个名为 Component 的类,并在其上有一个名为 draw 的方法。其他类,如 Button、Image 和 SelectBox,将从 Component 继承,从而继承 draw 方法。它们都可以重写 draw 方法来定义它们的自定义行为,但是框架可以将所有类型视为组件实例并对它们调用 draw。

因为 Rust 没有继承,我们需要另一种方式来构建 gui 库,以允许用户使用新类型扩展它。

定义共同行为的特征

Rust 提供泛型来支持抽象编程,但泛型要求类型在编译期可知。

trait 对象是另一种抽象方式:在运行时支持不同类型的值,前提是它们实现了某种 trait。

我们可以使用 trait 对象来代替泛型或具体类型。无论我们在哪里使用 trait 对象,Rust 的类型系统都会在编译时确保在该上下文中使用的任何值都将实现 trait 对象的 trait。因此,我们不需要在编译时知道所有可能的类型。

在 Rust 中,我们避免将结构体和枚举称为“对象”,以区别于其他语言的对象。在结构体或枚举中,结构体字段中的数据和 impl 块中的行为是分开的,而在其他语言中,组合成一个概念的数据和行为通常被标记为对象。

trait 对象更像其他语言中的对象,因为它们结合了数据和行为。但 trait 对象与传统对象的不同之处在于,我们不能向 trait 对象添加数据。trait 对象不像其他语言中的对象那样普遍有用,它们主要用于在通用行为上实现抽象。

通过指定某种指针(如 & 或 Box),然后指定 dyn 关键字,然后指定相关的 trait,可以创建 trait 对象。

为了实现我们希望 gui 拥有的行为,我们将定义一个 Draw trait,它将有一个名为 draw 的方法。然后我们可以定义一个接受 trait 对象的 Vec。trait 对象既指向实现指定 trait 的类型的实例,也指向用于在运行时查找该类型的 trait 方法的表。

Draw trait 的定义:

pub trait Draw {
    fn draw(&self);
}

定义一个名为 Screen 的结构体,它包含一个名为 components 的 Vec。这个 Vec 的类型是 Box,它是一个 trait 对象,是实现 Draw trait 的 Box 内任何类型的替代品。

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

在 Screen 结构体中,我们将定义一个名为 run 的方法,该方法将对它的每个组件调用 draw 方法.

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

这与定义使用带有 trait 约束的泛型类型参数的结构体不同。泛型类型参数一次只能被一个具体类型替代,而 trait 对象允许在运行时为 trait 对象填充多个具体类型。

例如,我们可以使用泛型类型和 trait 约束来定义 Screen 结构体:

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();
        }
    }
}

这将我们限制在一个 Screen 实例中,该实例具有所有类型为 Button 或所有类型为 TextField 的组件列表。如果只使用同构集合,那么使用泛型和 trait 约束是可取的,因为在编译时定义将是单态的,以便使用具体类型。

另一方面,使用 trait 对象的方法,一个 Screen 实例可以保存 Vec,其中包含 Box

你可能感兴趣的:(Rust,Rust)