以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

静态分发(static dispatch)

Rust中的静态分发(static dispatch)主要靠泛型来完成,对于不同的泛型类型参数,编译器会执行单态化处理,即在编译阶段就确定好应该调用的函数 ,为每一个被泛型类型参数代替的具体类型生成不同版本的函数和方法,这部分可以查看泛型部分进行了解

单态化(monomorphization)

单态化(monomorphization):即Rust编译器将一个泛型类型转化为多个非泛型类型的过程,编译器会在编译期间对每个实例进行单独优化,优化的结果是静态分发 有着(1)性能更好、(2)代码体积更小、(3)能保证类型安全等优点 但也存在(1)代码灵活度不高、(2)编译时间更长、(3)输出二进制文件更大等不足

动态分发(dynamic dispatch)

Rust中的动态分发(dynamic dispatch)可以使用户在不知道具体的类型的情况下,调用泛型类型上的Trait方法。动态分发通过Trait对象(Trait Object)完成,具体的实现机制是:Trait对象将实现了某Trait的类型的指针包装在一个特殊的结构体中,而该结构体又包含一个指向实现该Trait的类型的指针和一个指向Trait的虚函数表(vtable)的指针 ,结合下面的示意图可以更好地理解这段话Trait对象示意图

虚函数表(vtable)

虚函数表(vtable)是一个包含Trait方法的函数指针的数组,它允许在运行时动态调用Trait方法,当一个类型实现了一个Trait时,编译器会为该Trait生成一个虚函数表,并将该类型的实现添加到虚函数表中,即当一个对象被转换为Trait对象时,其会被分配一个指向该Trait的虚函数表的指针

Trait对象

要创建一个Trait对象,需要使用Box<dyn trait>&dyn Trait关键字将实现了某Trait的类型的指针包装在一个Box<dyn trait>&dyn trait类型的变量中。这个变量可以被传递给函数或存储在变量中,并在运行时动态分配,比如下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
trait Shape {
fn perimeter(&self) -> f64;
fn area(&self) -> f64;
}

struct Rectangle {
width: f64,
height: f64,
}

impl Shape for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn area(&self) -> f64 {
self.width * self.height
}
}

struct Circle {
radius: f64,
}

impl Shape for Circle {
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

fn main() {
let rec = return_trait_object(false);
println!("{:?}", rec.area());
}

// 以下的函数会返回一个Trait对象,这时用户不需关心返回的具体类型
// 只需要设置返回类型所需要满足的Trait
fn return_trait_object(flag: bool) -> Box<dyn Shape> {
if flag {
Box::new(Rectangle {
width: 11.0,
height: 9.0,
})
} else {
Box::new(Circle { radius: 7.0 })
}
}
// 可以把上面的例子和Trait部分最后的代码示例做一下对比,看一下有什么区别

但需要注意的是,动态分发相比于静态分发产生了更多的指针跳转操作,因此使用Trait对象时会在运行时产生一定额外的开销。实际情况下,用户应根据具体需求选择使用哪种分发模式

对象安全

在使用Trait对象时,应注意在Rust中,只能将对象安全(Object-Safe)的Trait转化为Trait对象 ,而对象安全的Trait应满足(1)方法返回的类型不是Self、(2)方法中不包含任何泛型类型参数 ,比如标准库中的Clone trait就是一个非对象安全的trait

1
2
3
4
// Clone trait返回类型是Self,所以它是一个非对象安全的trait
pub trait Clone {
fn clone(&self) -> Self;
}