三种多态形式
通俗来讲,只要是为不同类型的数据或操作提供了相同的名字就可以叫多态。
参数多态:
- 操作与类型无关
又叫编译时多态,Rust主要使用泛型来实现
对不同类型,调用相同方法,执行相同逻辑
参数化多态(Parametric polymorphism)是指一段代码适用于不同的类型,把类型作为参数,在实际需要的时候根据类型进行实例化,所有的实例有相同的行为
特设多态:
- 重写继承过来的指定方法, 就是实现接口方法
又叫ad-hoc,Rust主要使用Trait Impl实现。
ad-hoc是一个拉丁词,意思为特定,临时
Ad hoc是一个拉丁文常用短语。
这个短语的意思是“特设的、特定目的的(地)、即席的、临时的、将就的、专案的”。
这个短语通常用来形容一些特殊的、不能用于其它方面的,为一个特定的问题、任务而专门设定的解决方案。
称其为特设是因为这种多态并不像全称量化一样适用于所有类型,而是手动为某些特定类型提供不同的实现(这也就是trait impl过程)。
子类型多态
又叫运行时多态,Rust主要使用Trait Object实现
三种多态两两对比
参数多态 vs 特设多态
可以与参数多态对比一下:
- 相同点:对不同类型,调用相同方法
- 不同点:
- ad-hoc: 但是执行不同逻辑
- 参数多态:执行相同逻辑
参数多态 vs 子类型多态
- 在编程语言中,参数多态的主要实现方式有很多,Rust选择了泛型作为实现方式。
- 泛型的实现方式主要有擦除类型和单态化,前者是运行时操作,后者是编译时操作;
- Rust的泛型使用单态化实现,因此对应的参数多态也是发生在编译时,所以又被称为编译时多态;
- 而Trait Object的实现方式是运行时擦除类型 ,所以它其实是类似于泛型的另一种实现方式。
- java使用运行时擦除类型,其实就是在编译时替换为Object,进而保证类型安全。
- Rust使用运行时擦除类型,其实就是采用C++的方式,编译时替换为一个胖指针。
- 与C++不同的是,C++将虚函数表放在实例里,而Rust的胖指针指向实例和虚函数表
特设多态 vs 子类型多态
- 特设多态的实现很明确:特定实现,发生在编译期
- 子类型多态:胖指针实现,发生在运行期
具体再对比一下rust的两种多态实现方式:
trait impl(特设多态)vs trait object(子类型多态)
trait object(动态分发):
- 前提:trait里面的每个方法都要有&slef参数。
- 优势:使用指针,对参数的要求更灵活,二进制文件小。
- 劣势:运行时动态分发,造成性能不如静态分发。
- 特点:传指针或者引用。
trait bound(静态分发):
- 前提: 只要程序代码中并没初始化类型,那么针对这个类型的trait中的方法也没有生成。
- 执行:编译期分析代码,为每个实现了trait的类型执行单态化(必须满足前提)。
- 优势:编译期决定为类型分发trait实现,不影响运行时,性能高。
- 劣势:编译器为每个实现过trait的类型编译一份方法实现,二进制文件大。
- 特点:传值
具体到self和&self
#[warn(dead_code)] trait Animal { // trait object的方法都要带&self参数 fn hello(&self){println!("hello");} } struct Good{} struct Hello{} impl Animal for Good{}//默认实现 impl Animal for Hello{}//默认实现 //动态分发体现在传递引用&dyn fn test_dyn(x: &dyn Animal) { x.hello(); } //静态分发体现在传递值impl Animal fn test(x: impl Animal){ x.hello(); } fn main() { { let good = Good{}; test_dyn(&good); // let hello = Hello{}; // test_dyn(&hello); } }
- trait object的方法都要带&self参数
- 动态分发体现在参数传递引用&dyn
- 静态分发体现在参数传递值impl Animal