特征对象是动态调度的 Rust 实现。动态分派允许在运行时选择多态操作(特征方法)的一种特定实现。动态调度允许非常灵活的架构,因为我们可以在运行时交换函数实现。但是,与动态调度相关的运行时成本很小。
保存特征对象的变量/参数是胖指针,由以下组件组成:
- 指向内存中对象的指针
- 指向该对象的 vtable 的指针,vtable 是一个带有指向实际方法实现的指针的表。
例子
struct Point {
x: i64,
y: i64,
z: i64,
}
trait Print {
fn print(&self);
}
// dyn Print is actually a type and we can implement methods on it
impl dyn Print + 'static {
fn print_traitobject(&self) {
println!("from trait object");
}
}
impl Print for Point {
fn print(&self) {
println!("x: {}, y: {}, z: {}", self.x, self.y, self.z);
}
}
// static dispatch (compile time): compiler must know specific versions
// at compile time generates a version for each type
// compiler will use monomorphization to create different versions of the function
// for each type. However, because they can be inlined, it generally has a faster runtime
// compared to dynamic dispatch
fn static_dispatch<T: Print>(point: &T) {
point.print();
}
// dynamic dispatch (run time): compiler doesn't need to know specific versions
// at compile time because it will use a pointer to the data and the vtable.
// The vtable contains pointers to all the different different function implementations.
// Because it has to do lookups at runtime it is generally slower compared to static dispatch
// point_trait_obj is a trait object
fn dynamic_dispatch(point_trait_obj: &(dyn Print + 'static)) {
point_trait_obj.print();
point_trait_obj.print_traitobject();
}
fn main() {
let point = Point { x: 1, y: 2, z: 3 };
// On the next line the compiler knows that the generic type T is Point
static_dispatch(&point);
// This function takes any obj which implements Print trait
// We could, at runtime, change the specfic type as long as it implements the Print trait
dynamic_dispatch(&point);
}