1. 概述

假定咱们有这样的需求:创立一个GUI东西,它会遍历某个元素(指的是GUI原属)的列表,顺次调用元素的draw办法进行制作。例如Button、TextField等元素。

关于以上的需求,在面向目标的言语里

  • 咱们通常先界说一个Component父类,里面界说了draw办法
  • 接下来界说各个元素的类(Button、TextField等),它们都承继于Component类,再掩盖draw办法

而在rust里是没有承继功能的,所以说假如想用rust来构建这个GUI东西,咱们就得运用其他的办法,即为共有的行为界说一个trait。

2. 为共有行为界说一个trait

在rust里:

  • 咱们避免将struct或enum称为目标,虽然它们是持有数据,可是它们的办法实现是在impl块里,而struct和enum和impl块是分隔的。
  • 而trait目标有些类似于其他言语中的目标,由于trait目标在某种程度上实际上是组合了数据和行为。
  • trait目标与传统的目标不同的当地在于,咱们无法为trait目标添加数据。
  • trait目标被专门用于笼统共有行为的,它没有其他言语中的目标那么通用。

看一个示例代码:
lib.rs

pub trait Draw {
    fn draw(&self);
}
pub struct Screen {
    // 下面一行代码表明Box里的元素都实现了Draw trait
    // 所以只要实现了Draw trait的数据,都可以防汛Vec中
    // 之所以不运用泛型,应为泛型里只能存放一种元素
    pub components: Vec<Box<dyn Draw>>
}
impl Screen {
    pub fn run(&self) {
        // 遍历所有组件,履行draw办法
        for component in self.components.iter() {
            component.draw();
        }
    }
}
pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}
impl Draw for Button {
    fn draw(&self) {
        // 制作一个按钮
    }
}

main.rs

use trait_save_different_val::Draw;
use trait_save_different_val::{Button, Screen};
struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}
impl Draw for SelectBox {
    fn draw(&self) {
        // 制作一个挑选框
    }
}
fn main() {
    let screen = Screen {
        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.run();
}

3. trait目标履行的是动态派发

将trait约束作用于泛型时,rust编译器会履行单态化。编译器会为咱们用来替换泛型类型参数的每一个具体类型生成对应函数和办法的非泛型实现。

经过单态化生成的代码会履行静态派发(static dispatch),在编译过程中确认调用的具体办法。

所谓的动态派发(dynamic dispatch),它无法在编译过程中确认你调用的究竟是哪个办法,编译时会发生额定的代码以便在运行时找出期望调用的办法。假如运用trait目标,就会履行动态派发,那么将会导致发生运行时开销,而且阻止编译器内联办法代码,使得部分优化无法进行。

4. Trait目标必须保证目标安全

只能把满意目标安全(object-safe)的trait转化为trait目标。rust采用了一系列规则来断定某个目标是否安全,咱们只需要记住两条规则

  • 办法的回来类型不是Self
  • 办法中不包括任何泛型类型参数

咱们来看一个示例,在标准库中,Clone trait便是不符合目标安全的比如,Clone trait的clone办法如下

pub trait Clone {
    fn clone(&self) -> Self;
}

假如咱们在“2”中的示例代码运用Clone trait作为 Screen 结构体 components 的目标,如下代码

pub struct Screen {
    pub commponents: Vec<Box<dyn Clone>>
}

程序将会报错,由于Clone trait的clone办法回来Self,换句话说,它不是目标安全的。