一、静态分发:依靠泛型支持,实际上为通过编译期将泛型类型扩展为实际类型,实现单态,最后的结果是代码量的膨胀。
trait Foo {
fn method(&self) -> String;
}
impl Foo for u8 {
fn method(&self) -> String { format!("u8: {}", *self) }
}
impl Foo for String {
fn method(&self) -> String { format!("string: {}", *self) }
}
fn do_something<T: Foo>(x: T) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(x);
do_something(y);
}
//-----------------------------------------------------------------------------------------
// 在编译期,编译期会将泛型翻译为实际类型,针对具体类型分别实现函数,调用的时候调用的是各自的函数,如下
fn do_something_u8(x: u8) {
x.method();
}
fn do_something_string(x: String) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something_u8(x);
do_something_string(y);
}
二、动态分发:利用对象指针和函数指针的组合实现运行期的多类型分发
思路:
- 关键在于,当将trait impl到 对象之后,对象中重写的trait方法,会在虚函数表中存储该方法,
- 当通过(&对象 as &trait)后,便实现了指针连接,即将traitObject(胖指针)的一个指针指向对象,再将另一个指针指向一个虚函数表,
- 其中虚函数表中含有,在impl trait for 对象 时重写的方法地址和虚表的偏移量,将虚表地址和偏移量结合可以确认该对象所从写的trait方法
- 当调用该方法时,通过这个指针获取实际对象以及该实际对象重写的trait方法。
// 定义trait及方法
trait Bird {
fn fly(&self);
}
struct Duck;
struct Swan;
// 将trait impl到 Duck中,将重写的trait方法存入虚函数表
impl Bird for Duck {
fn fly(&self){
println!("duck duck");
}
}
// 将trait impl到 Swan中,将重写的trait方法存入虚函数表
impl Bird for Swan {
fn fly(&self){
println!("Swan Swan");
}
}
// 定义一个调用函数
// fn print_trait_obj(p: &dyn Bird){
// p.fly();
// }
fn main() {
// 新建对象
let duck = Duck;
// 创建 对象的引用
let p_duck = &duck;
// 将对象引用 转换成 trait对象,这个过程中——trait对象为胖指针(指针1.p_duck;指针2.(虚函数表+Duck的trait方法在虚函数表中的偏移量))
let p_bird = p_duck as &dyn Bird;
// 当调用trait方法时,从指针1中获取对象,从指针2中获取trait方法
// print_trait_obj(p_bird);
p_bird.fly(); // 因为fly(&self), 所以等价于 (p_bird.vtable.fly)(p_duck)
// 同理
let swan = Swan;
let p_swan = &swan;
let p_bird = p_swan as &dyn Bird; // 指针p_bird发生了重绑定
p_bird.fly();
// y 为struct
// let y = TraitObject {
//data存储实际值的引用
// data: &x,
// vtable存储实际类型实现Foo的方法
// vtable: &Foo_for_u8_vtable
// };
}```
网友评论