Trait 对象

语法
Trait对象类型 :
   dyn? [类型参数约束组]

Trait对象类型单约束 :
   dyn? Trait约束

trait 对象 是另一个类型的不透明值,该类型实现了一组 trait 。trait 集由一个 对象安全base trait 和任意数量的 [auto trait] 组成。

Trait 对象实现了 base trait ,它的 auto traits 以及 base trait 的任何 父级trait

Trait 对象写作关键字 dyn 后跟一组 trait 约束 ,但对 trait 约束有以下限制。 除了第一个 trait 之外的所有 trait 都必须是 auto trait ,不能有多个生命周期,并且不允许可选附加约束 (例如 ?Sized )。 此外,路径到 traits 可以括在括号中。

例如,给定 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 关键字。

注意: 为了清晰起见,建议在你的 trait 对象上始终使用 dyn 关键字,除非你的代码库支持使用 Rust 1.26 或更低版本进行编译。

版本差异: 在 2015 版中,如果 trait 对象的第一个限定是以 :: 开头的路径,那么 dyn 将被视为路径的一部分。你可以将第一个路径放在括号中来解决这个问题。 因此,如果你想要一个带有 trait ::your_module::Trait 的 trait 对象,你应该将其写为 dyn (::your_module::Trait)

从 2018 版开始, dyn 是正式的关键字,不允许在路径中使用,因此不需要括号。

如果 base trait 相互为别名, auto trait 集相同且生命周期约束相同,则两个 trait 对象类型为别名。例如, dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 是相同的。

由于值的具体类型的不透明性,trait 对象是 动态大小类型 。与所有 DSTs 一样,trait 对象在某种类型的指针后面使用,例如 &dyn SomeTraitBox<dyn SomeTrait> 。 指向 trait 对象的指针实例包括:

  • 指向实现 SomeTrait 的类型 T 的实例的指针
  • 虚方法表 (通常简称为 vtable ) ,它包含对于 T 实现的每个方法及其 父级trait ,指向 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 出现在 print 函数的类型签名和 main 函数中的转换表达式中,表示它们都是 trait 对象。

Trait 对象的生命周期约束

由于 Trait 对象可以包含引用,因此这些引用的生命周期需要作为 Trait 对象的一部分表示。这个生命周期的写法是 Trait + 'a 。有一些 默认值 可以允许此生命周期通常被推断为一个合理的选择。