rust trait对象

在拥有继承的语言中,可以定义一个名为shape的基类,该类上有一个draw方法。其他的类比如Button、SelectBox继承shape。它们各自覆盖draw方法。调用这些子类的draw方法时,就可以把它们统一当作shape来使用。不过Rust并没有继承,如果想做到这点,就得另寻出路。这个出路就是使用trait对象。trait对象的作用类似基类。

一、定义trait对象

(一)语法格式
trait对象是一种类型,定义语法如下

dyn 约束

约束是trait约束或者生存期约束。可以有多个约束,多个约束之间用+连接。
例如

dyn X
dyn X + Send
dyn X + Send + Sync
dyn X + 'static
dyn X + Send + 'static

X是一个trait

二、使用trait对象

trait对象是动态尺寸类型。像所有的 DST 一样,使用trait对象,必须使用它的指针类型;例如 &dyn SomeTraitBox。trait对象的指针包括:
一个指向实现SomeTrait的类型T的实例的指针
一个指向虚拟方法表的指针。虚拟方法表也通常被称为虚函数表,它包含了T实现的SomeTrait的所有方法,T实现的SomeTrait的父trait 的每个方法,还有指向T的实现的指针。

例子

pub trait Draw {
     fn draw(&self);
}
pub struct Screen {
     pub shapes: Vec>,     //存放trait对象的vector。Box就是trait对象,它指向一个实现了Draw的类型的实例
}

impl Screen {
     pub fn run(&self) {
          for shape in self.shapes.iter() {
              shape.draw();     //在每个shape上调用draw方法
          }
     }
}
pub struct Button {
     pub width: u32,
     pub height: u32,
     pub label: String,
}
impl Draw for Button {
     fn draw(&self) {
         // 实际绘制按钮的代码
     }
}
struct SelectBox {
     width: u32,
     height: u32,
     options: Vec,
}
impl Draw for SelectBox {
     fn draw(&self) {
         // code to actually draw a select box
     }
}
fn main() {
     let screen = Screen {
          shapes: 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();
}

从上面代码中可以看出,是不是与基类指针很类似?

run并不检查元素是Button或者SelectBox的实例。如果实例没有实现trait对象所需的trait则编译错误
例如,

fn main() {
     let screen = Screen {
         shapes: vec![
             Box::new(String::from("Hi")),
         ],
     };
     screen.run();
}

这个编译错误,因为String没有实现Draw

三、trait对象与泛型的区别

trait对象与带有trait约束的泛型结构体类似,但是也有不同。泛型参数一次只能替代一个具体类型,而trait对象则允许在运行时替代多种具体类型。
例如

pub struct Screen {
     pub shapes: Vec,
}
impl Screen
where T: Draw {
     pub fn run(&self) {
          for shape in self.shapes.iter() {
               shape.draw();
         }
     }
}

shapes是一个全是Button类型或者全是SelectBox类型的列表。总之,所有元素类型是相同的。
而使用trait对象,shapes能同时包含Box

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