Trait 对象

语法
TraitObjectType :
   dyn? 类型参数约束

TraitObjectTypeOneBound :
   dyn? Trait 约束

一个trait 对象是另一种类型的非透明值,它实现了一组 trait。这组 trait 由一个dyn compatible基础 trait以及任意数量的自动 trait组成。

Trait 对象实现基础 trait、其自动 trait 以及基础 trait 的任何超 trait

Trait 对象以关键字dyn开头,后跟一组 trait 约束,但对 trait 约束有以下限制。

不允许有多个非自动 trait,不允许有多个生命周期,且不允许使用 opt-out 约束(例如 ?Sized)。此外,trait 的路径可以放在括号中。

例如,给定 trait Trait,以下都是 trait 对象

  • dyn Trait
  • dyn Trait + Send
  • dyn Trait + Send + Sync
  • dyn Trait + 'static
  • dyn Trait + Send + 'static
  • dyn Trait +
  • dyn 'static + Trait.
  • dyn (Trait)

版本差异:在 2021 版本之前,可以省略关键字 dyn

版本差异:在 2015 版本中,如果 trait 对象的第一个约束是以 :: 开头的路径,那么 dyn 将被视为路径的一部分。可以通过将第一个路径放在括号中来避免这种情况。因此,如果你想要一个使用 trait ::your_module::Trait 的 trait 对象,你应该将其写为 dyn (::your_module::Trait)

从 2018 版本开始,dyn 是一个真正的关键字,不允许出现在路径中,因此不再需要括号。

如果基础 trait 互为别名,且自动 trait 的集合相同,并且生命周期约束相同,那么两种 trait 对象类型互为别名。例如,dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 相同。

由于其值具体是什么类型是非透明的,因此 trait 对象是动态大小类型。与所有DSTs一样,trait 对象通过某种类型的指针使用;例如 &dyn SomeTraitBox<dyn SomeTrait>。指向 trait 对象的指针的每个实例包括

  • 指向实现 SomeTrait 的类型 T 实例的指针
  • 一个虚方法表,通常简称为 vtable,其中包含对于 SomeTrait 及其超 trait 中由 T 实现的每个方法,一个指向 T 实现(即函数指针)的指针。

trait 对象的作用是允许方法的“晚绑定”。在 trait 对象上调用方法会导致运行时的虚派发:也就是说,从 trait 对象的 vtable 中加载一个函数指针并间接调用。每个 vtable 条目的实际实现可以因对象而异。

trait 对象示例

trait Printable {
    fn stringify(&self) -> String;
}

impl Printable for i32 {
    fn stringify(&self) -> String { self.to_string() }
}

fn print(a: Box<dyn Printable>) {
    println!("{}", a.stringify());
}

fn main() {
    print(Box::new(10) as Box<dyn Printable>);
}

在此示例中,trait Printable 作为 trait 对象出现在 print 的类型签名和 main 的强制转换表达式中。

Trait 对象生命周期约束

由于 trait 对象可以包含引用,因此这些引用的生命周期需要作为 trait 对象的一部分来表达。此生命周期写为 Trait + 'a。存在一些默认规则,允许通常以合理的选择来推断此生命周期。