rust_trait个人理解
0 概述
-
什么是trait
rust中有许许多多的类型(枚举、结构体...),如果这些不同类型,都有类似的行为,我们把这个行为抽象出来,把他定义为一个特征(trait)
一个trait中可以包含,一个或者一组行为,表现形式就是方法or函数
1 特征约束
1.1 特征约束
1.2 特征做函数参数的语法糖
真特么nb,看下面这个写法,这个意思就是所有实现了Summary
特征的对象都能作为参数传递进去
fn main() {
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
}
2 特征对象
实现了某个trait的实例,都被称为特征对象
比如我有weibo和post两个结构体,他们都实现了summary这个trait,那么他们俩的实例就是summary这个trait的特征对象
特征对象的这个映射关系,会存在一个表中,在运行时可以进行查询
可以通过 &
引用或者 Box<T>
智能指针的方式来创建特征对象
fn draw1(x: Box<dyn Draw>) {
x.draw();
}
fn draw2(x: &dyn Draw) {
x.draw();
}
draw1
函数的参数是Box<dyn Draw>
形式的特征对象,该特征对象是通过Box::new(x)
的方式创建的draw2
函数的参数是&dyn Draw
形式的特征对象,该特征对象是通过&x
的方式创建的dyn
关键字只用在特征对象的类型声明上,在创建时无需使用dyn
,dynamic
不知道你有没有发现,对于一个类型的设置,似乎有个dyn声明就能说明了,比如
x:dyn draw
。那么为什么rust要多次一举,写成智能指针形式?原因是特征对象可以是任意实现了某个特征的类型,编译器在编译期不知道该类型的大小,不同的类型大小是不同的。
如果写成
Box<dyn Draw>和&dyn Draw
这时候的指针大小是确定的
一个特征对象的大小=两个指针(ptr+vptr)
总之,特征对象的两个指针们可以理解为一个 数据指针和 行为指针
3 rust静态分发和动态分发
静态分发:rust的泛型在编译时,会为每一个可能的泛型生成具体的代码,所以运行时几乎没有任何损耗,这个方式时静态分发
动态分发:特征对象是属于动态分发的。因为在编译的时候,编译器无法知道所有特征对象的具体类型,只能依靠指针来找具体类型
- 一个指针
ptr
指向实现了特征Draw
的具体类型的实例,也就是当作特征Draw
来用的类型的实例,比如类型Button
的实例、类型SelectBox
的实例 - 另一个指针
vptr
指向一个虚表vtable
,vtable
中保存了类型Button
或类型SelectBox
的实例对于可以调用的实现于特征Draw
的方法。当调用方法时,直接从vtable
中找到方法并调用。之所以要使用一个vtable
来保存各实例的方法,是因为实现了特征Draw
的类型有多种,这些类型拥有的方法各不相同,当将这些类型的实例都当作特征Draw
来使用时(此时,它们全都看作是特征Draw
类型的实例),有必要区分这些实例各自有哪些方法可调用
注意
- 特征对象不同于实例对象,特征对象的vtable中只包含了实现Draw特征的方法
特征对象安全
不是所有的特征都能有特征对象,只有 对象安全的才有。
- 方法的返回类型不能是
Self
- 方法不带任何的泛型参数
假设你有一个trait,trait中某个方法返回的是一个Self。当你在使用特征对象调用这个trait的方法时,你根本不知道这个特征对象的具体类型,你只知道他实现了这个trait,所以就不可能知道Self是什么了,同样你也不知道
3 关联类型
我个人感觉关联类型是用来替代在trait中的泛型参数的。你可以把这个泛型参数写成一个单独的关联类型