Trait 对象

语法
TraitObjectType :
   dyn? TypeParamBounds

TraitObjectTypeOneBound :
   dyn? TraitBound

Trait 对象是另一种类型的不透明值,它实现了一组 trait。这组 trait 由一个 dyn 兼容基础 trait 加上任意数量的 auto trait 组成。

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

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

非 auto trait 不能超过一个,lifetime 不能超过一个,并且不允许选择退出约束(例如 ?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 关键字可以省略。

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

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

从 2018 版本开始,dyn 是一个真正的关键字,并且不允许在路径中使用,因此括号不是必需的。

如果基础 trait 互为别名,并且 auto trait 的集合相同,并且 lifetime 约束相同,则两个 trait 对象类型互为别名。例如,dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 相同。

由于值的具体类型是不透明的,trait 对象是 动态大小类型。像所有 DST 一样,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 Printableprint 的类型签名和 main 中的类型转换表达式中都作为 trait 对象出现。

Trait 对象 Lifetime 约束

由于 trait 对象可以包含引用,因此这些引用的 lifetime 需要作为 trait 对象的一部分来表达。此 lifetime 被写为 Trait + 'a。有一些 默认值 允许通常使用合理的选择来推断此 lifetime。