静态派发与动态派发 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) }