Skip to content

多态

如果你有其它语言的学习经验,可能常常听说多态,而多态分为静态多态和动态多态。这可以说是计算机界的元老级别八股文了,面试常考。

但是作为多范式语言,希望基于Rust带大家理解更加广泛的多态体系,而不是以简单的动静区分。

在类型系统里,多态性通常分为三类:

  • 参数多态(Parametric Polymorphism):属于静态多态,函数或类型对任意类型参数都能工作,不依赖具体实现。

    • Rust的泛型,impl trait
    • C++的模板
  • 特设多态(Ad-hoc Polymorphism):属于静态多态,同一个函数名在不同类型上有不同实现。

    • RustTrait
    • Haskelltypeclass
    • C++函数重载
  • 子类型多态(Subtype Polymorphism):属于动态多态,面向对象语言里常见,子类可以替代父类使用。

    • RustTrait对象
    • C++ 的虚函数
    • Java的继承

1.参数多态

通过 泛型 或 impl Trait 实现,编译期静态分发。

例如:

rust
// 泛型写法
fn swap<T>(a: &mut T, b: &mut T) {
    std::mem::swap(a, b);
}

// impl Trait 写法
fn print_debug(x: impl std::fmt::Debug) {
    println!("{:?}", x);
}

这种多态在编译期可以传入多种类型,并且每种类型最后的实现都是相同的,根据调用来进行单态化,从而产生符合各个类型的函数版本。

2.特设多态

通过 trait 为不同类型提供不同实现,本质就是函数重载

rust
trait Hello {
    fn hello(&self);
}

impl Hello for i32 {
    fn hello(&self) {
        println!("i32 says hello: {}", self);
    }
}

impl Hello for String {
    fn hello(&self) {
        println!("String says hello: {}", self);
    }
}

fn main() {
    let num = 666;
    let s = String::from("hello");

    num.hello();        // i32 says hello: 666
    s.hello();      // String says hello: hello
}

虽然Rust没有提供直接的函数重载,但是Trait可以看成一种函数重载。

比如Stringi32都能调用hello这个函数,可以理解为在hello这个同名函数中,你可以给第一个参数传入不同的self,从而调用到不同版本的函数。函数名相同,而根据不同参数来选择版本,这其实就是函数重载。

3.子类型多态

通过 trait object 实现,运行时动态分发。

rust
trait Shape {
    fn area(&self) -> f64;
}

struct Circle { r: f64 }
struct Square { a: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14 * self.r * self.r }
}

impl Shape for Square {
    fn area(&self) -> f64 { self.a * self.a }
}

fn print_area(s: &dyn Shape) {
    println!("area = {}", s.area());
}

Rust是一个混合范式语言,而不是一个面向对象语言,但是Rust汲取了面向对象的核心思想,把他们最重要的一些理论抽出来,做符合Rust的表达。

Rust中,几乎没有别的位置有对象这样的OOP风格表述。唯独dyn Trait这里使用了特征对象的表述,就是因为这里融合了非常类似于面向对象的多态思想。并且是以C++风格为主的多态思想。

你也许困惑为什么要叫虚表,为什么叫虚指针,为什么叫析构函数。对Rust来说,其实就是从C++中借鉴后,做出了一定的机制修改。

这里可以理解为Trait Object是一种父类型,而所有实现了Trait的类型都是子类型。父类型的引用或者指针,可以接收子类型的实例作为参数,然后运行时动态调用方法。因此 dyn Trait 从思想上就是一个子类型多态。

面向对象三大思想继承多态封装

首先Rust舍弃了继承,采用组合优先的思想。但是也有Trait继承这样的语法,以及刚才说的 dyn Trait 融合了继承的思想在里面。

在多态中,Rust有丰富全面的多态体系,你可以舍弃性能换取灵活性,你也可以舍弃灵活性以获取最高的性能,这一切都基于强大的Trait体系,它的知识点最庞杂。

对于封装来说,Rust有合理的函数封装体系,pub控制可见性,这一块知识点也会在后续讲解。

可见的是,Rust虽然不支持面向对象,但是其最核心,最精华的思想都已被Rust收入囊中。

MIT Licensed