Update chapter_3_10_2.md

This commit is contained in:
令狐一冲
2023-05-18 08:56:26 +08:00
committed by GitHub
parent 2cabfff8b9
commit 0bd4b2a862

View File

@@ -1,3 +1,133 @@
# 3.10.2 trait对象
todo
在上一节中第4点返回trait对象时提到了produce_item_with_name函数返回的是单一的类型即在编译时就确定了具体的类型因此produce_item_with_name无法正确编译。为解决这种问题Rust中引入了trait对象。
## 1. 使用trait对象
在Rust中trait自身不能当作数据类型来使用但trait 对象可以当作数据类型使用。因此可以将实现了Trait A的类型B、C、D当作trait A的trait对象来使用。使用trait对象时基本都是以引用的方式使用所以使用时通常是引用符号加dyn关键字即&dyn。示例如下
```Rust
trait GetName {
fn get_name(&self);
}
struct SchoolMember<'a>(&'a dyn GetName); // 学校成员是GetName trait对象
impl<'a> SchoolMember<'a> {
fn print_name(&self) {
self.0.get_name();
}
}
// Student是实现了GetName trait的类型
struct Student {
name: String,
}
impl GetName for Student {
fn get_name(&self) {
println!("name: {:?}", self.name);
}
}
// Teacher是实现了GetName trait的类型
struct Teacher {
name: String,
}
impl GetName for Teacher {
fn get_name(&self) {
println!("name: {:?}", self.name);
}
}
fn main() {
let alice = Student {
name: "alice".to_string(),
};
let bob = Teacher {
name: "bob".to_string(),
};
let sm1 = SchoolMember(&alice); // 把alice作为GetName trait对象传入
sm1.print_name();
let sm2 = SchoolMember(&bob); // 把bob作为GetName trait对象传入
sm2.print_name();
let chalie: &dyn GetName = &Student {
name: "chalie".to_string(),
};
chalie.get_name();
}
```
上面代码中Student和Teacher都实现了GetName trait因此这两种类型可以当做GetName的trait对象使用。
## 2. trait对象动态分发的原理
对于trait对象需要说明如下几点
- trait 对象大小不固定这是因为对于trait A类型B可以实现trait A类型C也可以实现trait A因此A trait对象的大小是无法确定的因为可能是B类型也可能是C类型
- 使用trait对象时总是使用它们的引用的方式
- 虽然trait对象没有固定大小但它的引用类型的大小固定它由两个指针组成因此占两个指针大小。
- 一个指针指向具体类型的实例。
- 另一个指针指向一个虚表vtablevtable中保存了实例对于可以调用的实现于trait的方法。当调用方法时直接从vtable中找到方法并调用。
- trait对象的引用方式有多种。例如对于trait A其trait对象类型的引用可以是&dyn A、Box<dyn A>、Rc<dyn A>等。
下面通过一段代码来分析一下使用trait对象时内存的布局。代码如下
```Rust
trait Vehicle {
fn run(&self);
}
// Car是实现了Vehicle trait的类型
// 只有一个字段表示车牌号
struct Car(u32);
impl Vehicle for Car {
fn run(&self) {
println!("Car {:?} run ... ", self.0);
}
}
// truck是实现了Vehicle trait的类型
// 只有一个字段表示车牌号
struct Truck(u32);
impl Vehicle for Truck {
fn run(&self) {
println!("Truck {:?} run ... ", self.0);
}
}
fn main() {
let car = Car(1001);
let truck = Truck(1002);
let vehicle1: &dyn Vehicle = &car;
let vehicle2: &dyn Vehicle = &truck;
vehicle1.run();
vehicle2.run();
}
```
vehicle1和vehicle1都是Vehicle trait对象的引用vehicle1来说trait对象的具体类型是Carvehicle2来说trait对象的具体类型是Truck
![](../../assets/13.png)
car和变量truck分别是Car类型和Truck类型vehicle1和vehicle2是Vehicle trait对象的引用ptr指向具体类型的实例vptr指向虚函数表
.rodatatrait对象的时候vehicle1.run()vehicle2.run()
## 3. trait对象要求对象安全
object safe trait trait trait的方法满足以下两条要求才是对象安全的
- Self
-
Selftrait对象在产生时Self具体是什么类型不知道Rust用带泛型的类型在编译时会做单态化trait对象是运行时才确定西西*
Clone返回的是Self
```Rust
pub struct Screen {
pub components: Vec<Box<dyn Clone>>,
}
```