rust_trait个人理解

发布时间 2023-09-16 15:28:29作者: wenli7363

0 概述

  1. 什么是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的泛型在编译时,会为每一个可能的泛型生成具体的代码,所以运行时几乎没有任何损耗,这个方式时静态分发

动态分发:特征对象是属于动态分发的。因为在编译的时候,编译器无法知道所有特征对象的具体类型,只能依靠指针来找具体类型

img

  • 一个指针 ptr 指向实现了特征 Draw 的具体类型的实例,也就是当作特征 Draw 来用的类型的实例,比如类型 Button 的实例、类型 SelectBox 的实例
  • 另一个指针 vptr 指向一个虚表 vtablevtable 中保存了类型 Button 或类型 SelectBox 的实例对于可以调用的实现于特征 Draw 的方法。当调用方法时,直接从 vtable 中找到方法并调用。之所以要使用一个 vtable 来保存各实例的方法,是因为实现了特征 Draw 的类型有多种,这些类型拥有的方法各不相同,当将这些类型的实例都当作特征 Draw 来使用时(此时,它们全都看作是特征 Draw 类型的实例),有必要区分这些实例各自有哪些方法可调用

注意

  1. 特征对象不同于实例对象,特征对象的vtable中只包含了实现Draw特征的方法

特征对象安全

不是所有的特征都能有特征对象,只有 对象安全的才有。

  1. 方法的返回类型不能是Self
  2. 方法不带任何的泛型参数

假设你有一个trait,trait中某个方法返回的是一个Self。当你在使用特征对象调用这个trait的方法时,你根本不知道这个特征对象的具体类型,你只知道他实现了这个trait,所以就不可能知道Self是什么了,同样你也不知道

3 关联类型

我个人感觉关联类型是用来替代在trait中的泛型参数的。你可以把这个泛型参数写成一个单独的关联类型