Trait Object
静态分派和动态分派
Rust支持静态分派(static dispatch)和动态分派(dynamic dispatch)。
静态分配是在编译阶段就确定好了应该调用哪个函数,它是通过泛型实现的多态。一个例子如下:
1 | trait Birt { |
上述例子就是利用泛型实现了多态,在编译阶段,编译器会根据实际调用的参数不同,直接生成不同的函数版本:
1 | // 伪代码 |
动态分派就是调用哪个函数必须在运行时才能决定。Rust中动态分派是靠Traint Object实现的,一个Trait Object就是执行Trait的指针,如果Bird是一个Trait,那么&Bird,Box
一个指向Trait的指针是一个胖指针,它不仅包含所指向的对象的元素,也包含一个虚函数表,用以在运行时决定这个Trait Object对应的函数实现调用。Rust中一个Trait Object的内部表示如下:
1 | pub struct TraitObject { |
Object Safe
Trait Object的构造受到很多约束,在约束不能满足的时候就会产生编译错误。
- trait有Self:Sized约束时,不能构造Trait Object。
如:
1 | trait Foo where Self: Sized { |
上述代码会产生编译错误。
如果Trait没有加上Self: Sized约束,而Trait中某一个某一个方法加上了Self: Sized约束,那么这个方法不会出现在Trait Object中。示例如下:
1 | trait Foo { |
- 当函数中有Self类型作为参数或者返回类型时(不包括self这个参数),不能构造Trait Object
有这样的一个例子:
1 | pub trait Clone { |
由于p时一个trait object,编译器只知道它是一个胖指针,包含了具体对象的指针以及指向虚函数表的指针。p指向具体的对象,它的类型是什么已经不知道了,编译器知道的只是这个对象实现了Clone这个Trait,而clone方法却要求返回一个与p指向的对象一样的类型的值,这个是无法完成的任务,所以这个Clone不是object safe的。
那么如果我们需要让一个Trait满足Object safe同时又要能够构造Trait Object该怎么办呢,还记得上面提到的Self: Sized约束吧,利用它就可以了。
1 | trait Double { |
这样之后我们就可以狗仔一个trait object了,当然这个trait object的虚函数表里不存在new这个函数。
- 当函数有泛型参数时,不能构造trait object
假如有这样的trait
1 | trait SomeTrait { |
如果我们使用trait object来调用这个函数:
1 | fn func(x: &SomeTrait) { |
这样会有一个问题,x是一个trait object,调用它的方法时是通过vtable虚函数表来进行查找并调用的,现在要调用的函数成了泛型函数,而泛型是在编译阶段自动展开的,generic_fn函数实际上有许多不同的版本,而把这些版本都塞进去是很困难的,Rust直接禁止使用trait object来调用泛型函数。
- 如果有静态方法,那么这个Trait不适Object safe的。
要构造这样的Trait Object,解决方法依然是利用Self: Sized约束。