特征对象

语法

TraitObjectType :

   dyn? TypeParamBounds

TraitObjectTypeOneBound :

   dyn? TraitBound

特征对象 是另一个类型的不透明值,该类型实现了一组特征。这组特征由一个 对象安全基础特征 加上任意数量的 自动特征 组成。

特征对象实现了基础特征、其自动特征以及基础特征的任何 超特征

特征对象写成关键字 dyn 后跟一组特征边界,但对特征边界有以下限制。除第一个特征外,所有特征必须是自动特征,生命周期不能超过一个,并且不允许使用退出边界(例如 ?Sized)。此外,特征的路径可以加括号。

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

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

版本差异:在 2015 版中,如果特征对象的第一个边界是以 :: 开头的路径,则 dyn 将被视为路径的一部分。第一个路径可以放在括号中以解决此问题。因此,如果您想要一个具有特征 ::your_module::Trait 的特征对象,则应将其写为 dyn (::your_module::Trait)

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

如果两个特征对象的基础特征相同,并且自动特征集和生命周期边界相同,则它们互为别名。例如,dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 相同。

由于值的具体类型是不透明的,因此特征对象是 动态大小类型。像所有 DST 一样,特征对象在某种类型的指针后面使用;例如 &dyn SomeTraitBox<dyn SomeTrait>。指向特征对象的每个指针实例都包含

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

特征对象的目的是允许方法的“后期绑定”。在特征对象上调用方法会导致在运行时进行虚拟调度:也就是说,从特征对象 vtable 加载函数指针并间接调用。每个 vtable 条目的实际实现可以根据对象的具体情况而异。

特征对象示例

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>);
}

在此示例中,特征 Printableprint 的类型签名和 main 中的强制转换表达式中都作为特征对象出现。

特征对象生命周期边界

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