Trait objects
trait对象
trait-object.md
commit: 364066f09c801acd45c656a345301f8cdb1e5870
本章译文最后维护日期:2022-08-21
句法
TraitObjectType :
dyn
? TypeParamBoundsTraitObjectTypeOneBound :
dyn
? TraitBound
*trait对象1*是另一种类型的不透明值(opaque value),它实现了一组 trait。2 这组 trait 是由一个对象安全的基础trait(base trait) 加上任意数量的自动trait(auto traits)组成。
trait对象实现了基础trait、它的自动trait 以及其基础trait 的任何超类trait(supertraits)。
trait对象被写为关键字 dyn
后跟一组 trait约束,这些 trait约束有如此限制:除了第一个 trait 外,其他所有 trait 都必须是自动trait;生存期不能超过一个;不允许选择退出约束(opt-out bounds)(例如 ?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
可以省略。注意: 除非你的代码库需要在 1.26 或更低版本的 Rust 下编译,否则为了表意清晰,强烈建议总是给 trait对象加上
dyn
关键字。
版次差异:在 2015 版里,如果 trait对象的第一个约束是以
::
开头的路径,那么dyn
会被视为路径的一部分。可以把第一条路径放在圆括号中来绕过这个问题。因此,如果希望 trait对象具有::your_module::Trait
路径,那么应该将其写为dyn (::your_module::Trait)
。从2018版开始,
dyn
是一个真正的关键字了,不允许在路径中使用,(不存在二义性了,)因此括号就没必要了。
如果基础trait 互为别名,并且自动trait 相同,生存期约束也相同,则这两种 trait对象类型互为别名。例如,dyn Trait + Send + UnwindSafe
和 dyn Trait + UnwindSafe + Send
是等价的。
由于值的具体类型是不透明的,trait对象是动态内存宽度类型。像所有的 DST 一样,trait对象常被用在某种类型的指针后面;例如 &dyn SomeTrait
或 Box<dyn SomeTrait>
。每个指向 trait对象的指针实例包括:
- 一个指向实现
SomeTrait
的(那个真实的不透明的)类型T
的实例的指针 - 一个指向*虚拟方法表(virtual method table_)*的指针。虚拟方法表也通常被称为 虚函数表(vtable),它包含了
T
实现的SomeTrait
的所有方法,T
实现的SomeTrait
的超类trait 的每个方法,还有指向T
的实现的指针(即函数指针)。
trait对象的目的是允许方法的“延迟绑定(late binding)”。在 trait对象上调用一个方法会导致运行时的虚拟分发(virtual dispatch):也就是说,一个函数指针从 trait对象的虚函数表(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
中的类型转换表达式(cast expression)中。
Trait Object Lifetime Bounds
trait对象的生存期约束
因为 trait对象可以包含引用,所以这些引用的生存期需要表示为 trait对象的一部分。这种生存期被写为 Trait + 'a
。默认情况下,可以通过合理的选择来推断此生存期。
本书行文中,使用“trait对象”这个词时,没区分 trait对象的值和类型本身,但一般都指类型。
译者认为这样翻译可能更容易理解:trait对象是丢失了/隐藏了具体真实类型的 trait实现的类型,此类型本身一般会实现了一组 trait。