一、Rust中的多态和泛型
在c++中多态是动态多态和静态多态的,静态多态基本是使用泛型或者宏来实现。而动态多态就是常见的虚表实现。在Rust中,也有类似的实现。它有两种分发 (dispatch) 机制:静态分发和动态分发。这就和c++中的模板和宏实现类似 ,优点是速度快但可能会导致代码膨胀(code bloat);动态当然就是利用虚表来实现了,这个的缺点是执行速度会受到影响,特别是在工程很大的情况下,确实是一个不小的 问题。
需要说明的是,在早期,动态分发和静态分发,采用的形式基本差不多,不同的在于编译器要自行判断是动态或是静态分发,但是在2018版本之后,就出现了dyn这个关键字,从而确保编译器以上级别的歧义性的消除。
二、impl Trait 和 dyn Trait
下面的网址是对dyn trait的一个基本说明:
https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/
在这个说明中,如果有过c++的虚表的基本知识的话,都应该会很容易的理解,Rust中对动态分发的原理。不过,文中也指出了它和c++的不同,在Rust中增加一层中间层的处理。尽量是在编译时,优先应用单态来实现(如果没有使用多态)。下面看一个基本例子说明
trait Trait {}
impl Trait for i32 {}
// old
fn function1() -> Box<Trait> { }
// new
fn function2() -> Box<dyn Trait> { }
dyn的出现可以理解为消除语言的模糊性,特别是在Rust中有Trait和Trait对象的不同,而在Trait对象中,需要对象必须是object safe(反之为object-unsafe或者not object-safe),而在Trait对象中,动态分发时需要dyn来处理,特别是返回值的处理,更需要清楚的表达具体的返回类型,这和Rust的单态性也不谋而合。
三、应用示例
看一下具体的例子就明白了:
静态分发:
trait Foo {
fn method(&self) -> String;
}
impl Foo for u8 {
fn method(&self) -> String { format!("u8: {}", *self) }
}
impl Foo for String {
fn method(&self) -> String { format!("string: {}", *self) }
}
fn do_something<T: Foo>(x: T) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(x);
do_something(y);
}
// 在编译期,编译期会将泛型翻译为实际类型,针对具体类型分别实现函数,调用的时候调用的是各自的函数,如下
fn do_something_u8(x: u8) {
x.method();
}
fn do_something_string(x: String) {
x.method();
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something_u8(x);
do_something_string(y);
}
动态分发:
// 定义trait及方法
trait Bird {
fn fly(&self);
}
struct Duck;
struct Swan;
// 将trait impl到 Duck中,将重写的trait方法存入虚函数表
impl Bird for Duck {
fn fly(&self){
println!("duck duck");
}
}
// 将trait impl到 Swan中,将重写的trait方法存入虚函数表
impl Bird for Swan {
fn fly(&self){
println!("Swan Swan");
}
}
// 定义一个调用函数
// fn print_trait_obj(p: &dyn Bird){
// p.fly();
// }
fn main() {
// 新建对象
let duck = Duck;
// 创建 对象的引用
let p_duck = &duck;
// 将对象引用 转换成 trait对象,这个过程中——trait对象为胖指针(指针1.p_duck;指针2.(虚函数表+Duck的trait方法在虚函数表中的偏移量))
let p_bird = p_duck as &dyn Bird;
// 当调用trait方法时,从指针1中获取对象,从指针2中获取trait方法
// print_trait_obj(p_bird);
p_bird.fly(); // 因为fly(&self), 所以等价于 (p_bird.vtable.fly)(p_duck)
// 同理
let swan = Swan;
let p_swan = &swan;
let p_bird = p_swan as &dyn Bird; // 指针p_bird发生了重绑定
p_bird.fly();
// y 为struct
// let y = TraitObject {
//data存储实际值的引用
// data: &x,
// vtable存储实际类型实现Foo的方法
// vtable: &Foo_for_u8_vtable
// };
}```
以上代码出自:
https://www.jianshu.com/p/6a6cfa4a47df
非常感谢!如有不妥,请指出及时删除!
从上面的代码可以清晰的看出,动态分发和静态分发的不同,如果能有其它语言的基础,对比来分析学习,则可能会更准确的把握Rust的相关技术点。
四、总结
其实如果看多了语言,一定会明白它们之间是勾勾连连的,没有说一个语言完全和其它语言独立而互相没有借鉴或者说没有想通的地方的。如果有,那一定是极早期或者个虽的特立独行的语言。流行的语言,一定是对以前语言的丰富和简化,否则,想流行起来,难度还是非常大的。就如Rust,如果真得大规模的普及起来,反而会有些惊诧!
努力吧,归来的少年!