前言
在CPP语言中,为了代码的灵活性和复用性,模板和继承中的父类指针指向子类引用的实现方式被开发者喜欢,对于Rust来说,便是泛型,特征约束和dyn关键字。他们的目的类似,只不过在特定的语言范围内使用方式不大相同。在rust泛型与特征一文中,我们讨论了Box< dyn Trait >的写法,本文做进一步的补充说明,这里关注于代码举例与细节对比
Dynamically Sized Types
一般实现了trait Sized的类型在编译期就知道他们的大小,而对于在runtime期间在可以知道他们的真实大小的类型就是DST。而对于Rust编译期来说,安全是非常重要的,每一种类型在编译期都需要知道Sized才能进入编译否则报错,不论你是变量、函数参数,还是其他的什么。那么对于本章讨论的动态派发中的特征对象,也逃不过这样的限制,开发者有两种选择,或是使用&引用,或是使用Box智能指针。
指向切片和特征对象的指针与其他的有什么区别
其最大的区别就是,指向切片的指针同时保存了切片中包含数据的数量,指向特征对象的指针,一个指向了自己的特定类型,同时保留了一个虚表指针用于指向虚表,所以这两种指针是普通指针的两倍大小,从而在runtime期间可以精准的让特定的类型调用到特定的方法(虚表已经在泛型与特征一文中有所表述,这里不再赘述)
特征约束的代码实现
trait Sound {
fn make_sound(&self);
}
struct Cat;
impl Sound for Cat {
fn make_sound(&self) {
println!("Meow!");
}
}
struct Dog;
impl Sound for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}
fn make_sound<Animal: Sound>(a: &Animal) {
a.make_sound();
}
fn main() {
let dog = Dog;
let cat = Cat;
make_sound(&dog);
make_sound(&cat);
}
特征对象动态派发代码实现
trait Sound {
fn make_sound(&self);
}
struct Cat;
impl Sound for Cat {
fn make_sound(&self) {
println!("Meow!");
}
}
struct Dog;
impl Sound for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}
fn make_sound(animal: &dyn Sound) {
animal.make_sound();
}
fn main() {
let dog = Dog;
let cat = Cat;
make_sound(&dog);
make_sound(&cat);
}
什么时候只能用动态派发
当方法内部需要返回不同类型的时候,如下:
fn something(xx)->impl Sound{
if {
return Dog;
}else{
return Cat;
}
}
这样的是不成立的,大多数文章会写因为类型需要在编译期确定,这看起来似乎是解释了原因,那么在这里多问一句,这怎么就不是编译期确定的类型呢?因为返回值类型有两个,编译器无法像函数调用一样,通过参数的不同(Dog or Cat),从而在编译期间确定参数类型是什么,而使用特征约束的,只能确定一个类型,要么Dog要么Cat,都有的话,编译期会认为一个值有两种类型从而报错。
正确的使用方法如下:
fn something(xx)->Box<dyn Sound>{
if {
return Box::from(Dog);
}else{
return Box::from(Cat);
}
}