多态性
我们一开始讲过面向对象的继承以及RUST 为什么不愿意使用继承,继承中会大量用到多态的能力,让我们先尝试分析一下 传统面向对象语言中的多态实现
静态分发
当执行一个类型的方法时,必须要知道该方法指向的函数地址(又叫函数指针),访问某个类型的某个方法称之为分发
让我们回顾上一个小节的一段代码
trait AnimalSound {
fn sound(&self);
}
struct Cat;
struct Dog;
impl AnimalSound for Cat {
fn sound(&self) { //假设该函数被声明为: Cat_AnimalSound_sound
println!("miao miao");
}
}
impl AnimalSound for Dog {
fn sound(&self) { //假设该函数被声明为: Dog_AnimalSound_sound
println!("wang wang");
}
}
//主人拥有一个宠物
struct Master<T>{pet:T}
impl<T: AnimalSound> Master<T> {
//主人可以命令宠物发声音
fn command_sound(&self) {
let Master{pet} = self;
pet.sound();
}
}
//下面代码时编译器通过静态替换实现的
struct Master<Cat>{pet:Cat}
impl Master<Cat> {
//主人可以命令宠物发声音
fn command_sound(&self) {
let Master{pet} = self;
//这里调用 Cat_AnimalSound_sound
pet.sound();
}
}
//
fn main(){
//当使用泛型时 RUST会声明静态的类型
let master = Master{pet: Cat};
master.command_sound();
}
上述动作 在RUST 中叫做静态分发,一个典型的场景就是当一个类型 如果使用的特征有重名函数,必须使用完全限定语法
// 定义两个特征
trait TraitA {
fn shared_method(&self);
}
trait TraitB {
fn shared_method(&self);
}
// 实现特征 TraitA 的结构体
struct MyStruct;
impl TraitA for MyStruct {
fn shared_method(&self) {
println!("TraitA's implementation");
}
}
// 实现特征 TraitB 的结构体
struct AnotherStruct;
impl TraitB for AnotherStruct {
fn shared_method(&self) {
println!("TraitB's implementation");
}
}
fn main() {
// 创建结构体实例
let my_instance = MyStruct;
let another_instance = AnotherStruct;
// 调用 TraitA 中的方法
TraitA::shared_method(&my_instance);
// 调用 TraitB 中的方法
TraitB::shared_method(&another_instance);
}
动态分发
动态分发在C++ 和 JAVA 中被广泛使用,它允许一个类型(使用了接口 Interface)的时候 会采用动态分发
我们知道 RUST trait 是一组函数指针的描述,该描述和 Interface 很像,先让我们看一下java 中的接口调用

vtable 一般又叫做虚函数表,在编译阶段,当传入类型是Parent时候,无法知道实际的函数实现,只能在动态运行过程中, 通过遍历实际child类型的虚函数表,才知道函数地址
由于需要查找vtable 所以动态分发的方式需要更多的资源开销
特征对象
这部分也可以在学习完 内存安全 以及 不定长对象之后再回来看
RUST 中的特征对象 被定义为: 丢失了/隐藏了具体真实类型的 trait实现的类型,此类型本身一般会实现了一组 trait。 几乎可以认为trait 对象描述 特征的对象,由于具体的函数指针列表 可能会因为 trait 不同,而具有不同的函数指针列表, 因此该对象是一个不定长对象(关于不定长对象 我们后面会讲 暂时理解为一个 类型,但是该类型的内存长度不是确定的)
使用特征对象,需要明确使用 'dyn trait_name' 表示类型,由于该类型的不定长,因此只能使用引用 或者是 放在智能指针里面使用
use std::fmt::Display;
//也可以使用 T:Display,但是这两种方式完全是不同的效果,一个是动态查找函数,一个是静态分发
fn show_me(item: &dyn Display) {
println!("{}",item);
}
fn main() {
show_me(&32);
}
使用特征对象,会丧失原有类型的能力,只能使用 动态对象中的函数