Search CTRL + K

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 中引用/指针只有两种类型:

将 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 来曲线实现。

Trait objects

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]

Why each trait has an implicit ?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]

Why are traits ?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]

Why type parameters has an implicit 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]

relaxed bound ?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

  1. https://doc.rust-lang.org/reference/types/trait-object.html ↩︎

  2. https://stackoverflow.com/a/30941589 ↩︎ ↩︎

  3. https://github.com/pretzelhammer/rust-blog/blob/master/posts/sizedness-in-rust.md ↩︎ ↩︎