静态派发与动态派发 Static Dispatch and Dynamic Dispatch

一句话概括它们的特点:

  • 静态派发 (static dispatch), 是在编译期间确定函数调用关系, 是所谓的编译期多态 compiling time polymorphism
  • 动态派发 (dynamic dispatch), 是在程序运行期间, 通过对象的虚表 (vtable), 找到要调用的目标函数, 并调用它, 是所谓的运行时多态 runtime polymorphism

可以看到, 动态派发多了查找虚表的操作, 要比静态派发慢一点点, 但是动态派发使用起来更加灵活.

举例来说:

pub struct Point {
    x: f32,
    y: f32,
}

pub trait Print {
    fn print(&self);

    fn default_method(&self) {
        println!("Print::default_method()");
    }
}

impl dyn Print + 'static {
    fn print_trait_object(&self) {
        println!("Print::print_trait_object()");
    }
}

impl Print for Point {
    fn print(&self) {
        println!("Point({}, {})", self.x, self.y);
    }
}

fn static_dispatch<T: Print>(point: &T) {
    print!("static_dispatch: ");
    point.print();
    point.default_method();
}

fn dynamic_dispatch(point: &(dyn Print + 'static)) {
    print!("dynamic_dispatch: ");
    point.print();
    point.default_method();
    point.print_trait_object();
}

fn main() {
    let p = Point { x: 3.0, y: 4.0 };
    static_dispatch(&p);
    println!("==============");
    dynamic_dispatch(&p);
}

impl trait

有时候使用 trait 作为函数返回值的类型:

#![allow(dead_code)]

mod static_dispatch_input;

fn double_positive(numbers: &[i32]) -> impl Iterator<Item=i32> + '_ {
    numbers.iter().filter(|x| x > &&0).map(|x| x + 2)
}

fn combine_vecs(u: Vec<i32>, v: Vec<i32>) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u).cycle()
}

fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    move |x: i32| x + y
}

fn main() {}

只要 trait 或者相应的类型在本 crate 中有声明, 就可以给这个类型实现这个 trait.

作为传入参数

先看一个例子, 用静态派发的方式将 trait 作为函数的传入参数:

#![allow(unused)]
fn main() {
}

接下来我将把这个例子改造成动态派发的形式:

#![allow(unused)]

fn main() {
}

作为函数返回值

先看一个例子, 用静态派发的方式将 trait 作为函数的返回值:

#![allow(unused)]
fn main() {
}

接下来我将把这个例子改造成动态派发的形式:

#![allow(unused)]


fn main() {
# 参考

- [trait object](https://stackoverflow.com/questions/27567849/what-makes-something-a-trait-object)
}