Trait Object
trait 对象 是另一种类型的不透明值 (opaque value),它实现了一组 trait。这组 trait 是由一个 动态兼容 的基础 trait (base trait) 加上任意数量的自动 trait (auto traits) 组成。
trait 对象(trait object) 是 unsized 类型,因此动态兼容要求超类 trait 中不能有 Sized
。
Sized
约束条件
Sized
是 rust 中的自动 trait 和标记 trait。
Size
并不是真正的自动 trait
事实上 Sized
并不是真正的自动 trait,因为其定义中并没使用 auto
关键词,而是编译器特殊实现自动能力。
trait 默认都是 ?Sized
放宽约束的,因为 trait 可以在 unsized 类型上实现。
trait 对象限制
不能将 unsized 类型转为 trait 对象
fn generic<T: ToString>(t: T) {}
fn trait_object(t: &dyn ToString) {}
fn main() {
genericfrom("String"); // ✅
generic("str"); // ✅
trait_objectfrom("String"); // ✅ - unsized coercion
trait_object("str"); // ❌ - unsized coercion impossible
}
在 rust 中引用/指针只有两种类型:
- 指向 sized 对象的:1 字长
- 指向 unsized 对象的(宽指针):2 字长
将 unsized 类型(宽指针,一字指向地址;一字指向长度)转为 triat 对象(宽指针,一字指向地址;一字指向 vtable),需要三字同时存储地址、长度和 vtable,这是不支持的。
Type | Pointer to Data | Data Length | Pointer to VTable | Total Width |
---|---|---|---|---|
&String | ✅ | ❌ | ❌ | 1 ✅ |
&str | ✅ | ✅ | ❌ | 2 ✅ |
&String as &dyn ToString | ✅ | ❌ | ✅ | 2 ✅ |
&str as &dyn ToString | ✅ | ✅ | ✅ | 3 ❌ |
不能创建多基础 trait 对象
trait Trait {}
trait Trait2 {}
fn function(t: &(dyn Trait + Trait2)) {}
同样是宽指针 2 字长的问题,多个基础 trait 对象会需要多个字存储不同 vtable,这导致需要更多字长存储信息。
可以创建一个同时被 Trait、Trait2 两个 trait 界定的 Trait3 来曲线实现。
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of a dyn compatible base trait plus any number of auto traits.[1]
?Sized
bound on Self
?
This is where the question of sizedness comes into the picture. Is the Self
parameter sized?
It turns out that no, Self
is not sized by default in Rust. Each trait has an implicit ?Sized
bound on Self
. One of the reasons this is needed because there are a lot of traits which can be implemented for unsized types and still work. For example, any trait which only contains methods which only take and return Self
by reference can be implemented for unsized types.[2]
?Sized
by default?
The answer is trait objects. Trait objects are inherently unsized because any type of any size can implement a trait, therefore we can only implement Trait
for dyn Trait
if Trait: ?Sized
.[3]
Sized
bound?
In Rust all generic type parameters are sized by default everywhere - in functions, in structs and in traits. They have an implicit Sized
bound; Sized
is a trait for marking sized types:
fn generic_fn<T: Sized>(x: T) -> T { ... }
This is because in the overwhelming number of times you want your generic parameters to be sized.[2:1]
?Size
Some tips on ?Size
:[3:1]
?Sized
can be pronounced "optionally sized" or "maybe sized" and adding it to a type parameter's bounds allows the type to be sized or unsized?Sized
in general is referred to as a "widening bound" or a "relaxed bound" as it relaxes rather than constrains the type parameter?Sized
is the only relaxed bound in Rust