美文网首页
Rust for cpp dev - 使用 Trait 对象实现

Rust for cpp dev - 使用 Trait 对象实现

作者: 找不到工作 | 来源:发表于2021-05-23 16:50 被阅读0次

假设我们希望设计一个 GUI 库,对于每一个组件,我们希望能调用 draw() 方法来显示。

对于传统的有“继承”特性的语言,可以让所有组件都继承自一个 Component 类,这个类有一个纯虚函数 draw(),然后每个组件实现各自的 draw() 方法。

然而 Rust 没有继承的概念,如果用泛型来实现:

pub struct StaticScreen<T: Draw> {
    pub components: Vec<T>,
}

impl<T: Draw> StaticScreen<T> {
    pub fn show(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

这样的话,StaticScreen 类型只能存放一种类型的组件。例如Button。不能满足需求。

我们需要一个新的方式来实现,即 Trait 对象。

定义 trait 对象

我们需要定义一个 Draw trait,然后定义一个 vector,其中的对象都是实现了 Draw trait 的。每个 trait 对象指向两个东西:

  • 一个实现了 Draw trait 的类型的实例
  • (类似于 cpp 中的虚表)一个在 runtime 查找 trait 方法的表

trait 对象不能拥有数据成员,它的作用仅是允许抽象出公共的行为。 其定义方法是:

Box<dyn T>>

例如,对于这里的需求,可以如下定义:

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

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

这样,components 就是一个由实现了 Draw trait 的智能指针构成的 vector。可以如下使用:

use gui::{DynamicScreen, Button, SelectBox};

fn main() {
    let screen = DynamicScreen {
        components: vec![
            Box::new(SelectBox {
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No"),
                ],
            }),
            Box::new(Button {
                width: 50,
                height: 10,
                label: String::from("OK"),
            }),
        ],
    };

    screen.show();
}

可以看出,screen 中保存了两个类的实例,分别是 ButtonSelectBox。他们的共同特点是都实现了 Draw trait。运行时会调用各自的 draw() 函数。

// component definition

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        println!("Draw a button");
    }
}

pub struct SelectBox {
    pub width: u32,
    pub height: u32,
    pub options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!("Draw a select box");
    }
}

结果是:

Draw a select box
Draw a button

Rust 中,我们并不关心 ButtonSelectBox 之间有什么关系,只要他们都实现了 draw() 方法,就可以调用。这个思路和 golang 相似,都是基于 duck typing 的概念——如果它走路像样子,叫声也像鸭子,那它就是鸭子。

这样做的优势是我们不需要在运行时去检查是否实现了一个方法(cpp 中的 dynamic_cast),如果没有实现 Draw trait,它不会通过编译。

trait 对象的开销

trait 对象实际上起到了 cpp 中的虚函数的作用,因此也有类似的额外开销。

在上面的例子中,编译器无法在编译时知道要调用的对象的类型,因为这些对象是在一个动态数组中的,实际使用时,可能会根据用户输入变化。所以,它只能在运行时通过 trait 对象内部的指针来查找需要调用的方法。

相关文章

  • Rust for cpp dev - 使用 Trait 对象实现

    假设我们希望设计一个 GUI 库,对于每一个组件,我们希望能调用 draw() 方法来显示。 对于传统的有“继承”...

  • Rust for cpp dev - Trait 的高级用法

    在之前的章节中,我们的代码中出现了一些 trait 的用法,但是并没有展开来讲。本章我们将集中介绍 trait 的...

  • Rust for cpp dev - 宏

    宏是一种可以生成代码的代码,这种形式被称为“元编程”(metaprogramming)。我们已经使用过 Rust ...

  • Rust for cpp dev - 线程池

    在 web server[https://www.jianshu.com/p/35d9fd027dfd] 项目中,...

  • Rust学习——trait对象

    一、静态分发:依靠泛型支持,实际上为通过编译期将泛型类型扩展为实际类型,实现单态,最后的结果是代码量的膨胀。 二、...

  • Rust for cpp dev - web server 项目

    作为最终项目,我们需要建一个 web server。大致包含的内容是: 在一个 socket 上监听 TCP 连接...

  • 为trait object和trait constraint实现

    在Rust里,我们一般倾向于为一个具体的类型实现实现trait,然而,还有一种鲜为人知的写法,为泛型实现trait...

  • rust Iterator

    Rust Iterator设计: 定义: 对Iterator Trait的理解: Rust的Iterator在大部...

  • [Rust]Trait

    trait定义了某一个类型所具有的特定行为,跟Java中的抽象类有类似,但有一些区别。trait中可以包含常量,函...

  • Rust Trait

    观感 Rust的Trait和Golang的interface看起来非常相似,从开发者角度来看,都可以实现具体类型的...

网友评论

      本文标题:Rust for cpp dev - 使用 Trait 对象实现

      本文链接:https://www.haomeiwen.com/subject/mdoajltx.html