• rust trait对象


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

    一、定义trait对象

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

    dyn 约束
    
    • 1

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

    dyn X
    dyn X + Send
    dyn X + Send + Sync
    dyn X + 'static
    dyn X + Send + 'static
    
    • 1
    • 2
    • 3
    • 4
    • 5

    X是一个trait

    二、使用trait对象

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

    例子1

    trait Bar {
         fn baz(&self);
    }
    #[derive(Debug)]
    struct Foo;
    impl Bar for Foo {
         fn baz(&self) {
             println!("{:?}", self);
         }
    }
    #[derive(Debug)]
    struct Joo;
    impl Bar for Joo {
         fn baz(&self) {
             println!("{:?}", self);
         }
    }
    fn dynamic_dispatch(t: &dyn Bar) {
         t.baz();
    }
    fn main() {
         let foo = Foo;
         dynamic_dispatch(&foo);
         let joo = Joo;
         dynamic_dispatch(&joo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    例子2

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

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

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

    fn main() {
         let screen = Screen {
             shapes: vec![
                 Box::new(String::from("Hi")),
             ],
         };
         screen.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    三、trait对象与impl trait的区别

    impl trait只能用于函数参数和返回值;
    trait对象可以用在所有地方。

    当在函数中使用时,两者是一样的。
    例子

    trait Bar {
         fn baz(&self);
    }
    #[derive(Debug)]
    struct Foo;
    impl Bar for Foo {
         fn baz(&self) {
             println!("{:?}", self);
         }
    }
    #[derive(Debug)]
    struct Joo;
    impl Bar for Joo {
         fn baz(&self) {
             println!("{:?}", self);
         }
    }
    fn dynamic_dispatch(t: &dyn Bar) {
         t.baz();
    }
    fn dis(t: &impl Bar){
         t.baz();
    }
    fn main() {
         let foo = Foo;
         let joo = Joo;
         dis(&foo);
         dis(&joo);
         dynamic_dispatch(&foo);
         dynamic_dispatch(&joo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    而在其他地方,两者就不一样了,比如上一节的例子2,只能使用trait对象,不能使用impl trait。

    四、trait对象要求对象安全

    只有对象安全的trait才可以组成trait对象。如果一个trait中所有的方法有如下属性时,则该trait是对象安全的:
    1.返回值类型不为Self
    2.方法没有任何泛型参数
    Self代表自身。对象安全对于trait对象是必须的,因为一旦有了trait对象,就不再知晓实现该trait的具体类型是什么了。如果trait方法返回具体的Self类型,但是trait对象忘记了其真正的类型,那么方法不可能使用已经忘却的原始具体类型。同理对于泛型类型参数来说,当使用trait时其会放入具体的类型参数:此具体类型变成了实现该trait的类型的一部分。当使用trait对象时其具体类型被抹去了,故无从得知放入泛型参数类型的类型是什么。
    一个trait的方法不是对象安全的例子是标准库中的Clone trait。

    pub trait Clone {
         fn clone(&self) -> Self;
    }
    
    • 1
    • 2
    • 3

    String实现了Clone trait,当在String实例上调用clone方法时会得到一个String实例。类似的,当调用Vec 实例的clone方法会得到一个Vec 实例。clone的签名需要知道什么类型会代替Self,因为这是它的返回值。
    如果尝试做一些违反对象安全规则的事情,编译器会提示你。
    例如,

    pub struct Screen {
         pub components: Vec>,
    }
    
    • 1
    • 2
    • 3

    将会得到如下错误:
    error[E0038]: the trait std::clone::Clone cannot be made into an object
    Clone不能组成trait对象。

  • 相关阅读:
    D. Yet Another Problem
    华为机试真题 Java 实现【玩牌高手】
    再来了解机器学习和深度学习_有监督机器学习_无监督机器学习---人工智能工作笔记0018
    MySQL数据库期末考试试题及参考答案(09)
    React-Hooks详解
    让GPT成为您的科研加速器丨GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图
    Java - SpringBoot整合Shiro之缓存功能
    升降机用三级液压缸的设计与仿真
    NLP从零开始------9文本进阶处理之文本相似度计算
    Django04_路由分发
  • 原文地址:https://blog.csdn.net/inxunxun/article/details/133191811