You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.8 KiB

Trait:定义共享行为

trait定义了特定类型所具有的功能,并且可以与其他类型共享。我们可以用特征来抽象地定义共享行为。我们可以使用trait边界来指定泛型类型可以是具有特定行为的任何类型。

注意:trait类似于其他语言中称为接口的特性,尽管有一些不同。

定义一个Trait

类型的行为由我们可以调用该类型的方法组成。如果我们可以对所有这些类型调用相同的方法,不同的类型就会共享相同的行为。Trait定义是一种将方法签名分组在一起的方法,以定义完成某些目的所必需的一组行为

pub trait Summary {
    fn summarize(&self) -> String;
}

trait关键字和一个名称来声明trait。我们还将这个trait声明为pub,这样依赖于这个crate的crate也可以使用这个trait,正如我们将在几个例子中看到的那样。

在方法签名之后,我们使用分号,而不是在花括号内提供实现。实现此特征的每个类型必须为方法体提供自己的自定义行为

trait的主体中可以有多个方法:每行列出一个方法签名,每行以分号结束。

实现一个Trait

在impl之后,我们放入想要实现的trait名称,然后使用for关键字,然后指定想要为其实现trait的类型的名称。

image-20230216100752058

lib位置,以及Cargo.toml中配置

image-20230216101013803

默认实现

为trait中部分或所有方法设置默认行为,而不是要求每种类型的所有方法都实现默认行为很有用。只需实现非默认的行为。

image-20230216135657178

默认方法不需要在特定结构中实现就可以使用。同时默认方法可在特定结构里重写。

image-20230216141716908

特征作为参数

使用特征来定义接受多种不同类型的函数。

image-20230216150138176

Trait Bound语法

pub fn notify<T: Summary>(item: &T){
	println!("Breaking news! {}", item.summarize());
}

单参数:

image-20230216160527803

多参数:

image-20230216161450693

若想限制只能同一结构,可以只是用一个泛型参数T。

使用+语法指定多个Trait Bounds

感觉就类似于实现多个接口。

普通格式:

pub fn notify(item: &(impl Summary + Display)) {

泛型格式(更好):

pub fn notify<T: Summary + Display>(item: &T) {

使用where子句明确Trait Bounds

每个泛型都有自己的trait bounds,因此具有多个泛型类型参数的函数在函数名和参数列表之间可能包含大量trait bounds信息,使得函数签名难以阅读。出于这个原因,Rust在函数签名之后的where子句中使用了另一种语法来指定trait bounds。

不推荐的写法:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

更推荐的写法:

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    unimplemented!()
}

返回类型为特征

image-20230216164105767

若想返回属于此特征的单个类型,使用if-else会产生如下错误:if and else have incompatible types 原因是:当 switch 为真时返回了一个 NewArticle 类型的值,当 switch 为假时返回了一个 Tweet 类型的值。由于这两个类型都实现了 Summary trait,因此我们可以将它们统一用 impl Summary 来返回。然而,由于 NewArticleTweet 的类型不同,导致了 ifelse 两个分支的返回类型不一致,因此编译器会报错。

image-20230216172735181

正确写法:返回类型改为 Box<dyn Summary>,其中 dyn 是 Rust 中表示 trait 对象的关键字。这个函数现在可以正常运行,而且它可以在运行时动态选择要返回的类型,这也是 trait 对象的一个优点。

image-20230217140734278

Box<dyn Summary> 表示一个指向实现了 Summary trait 的类型的堆上分配对象的指针,由 Box 类型进行管理。其中,dyn 是 Rust 的关键字,表示动态分发(dynamic dispatch),也就是运行时才决定调用哪个实现。

在 Rust 中,由于不同的类型大小可能不同,因此不能在栈上直接存储一个具体类型的值。通过使用 Box 类型可以在堆上分配空间来存储一个值,并将这个值的所有权交给 Box,由 Box 负责管理这块内存,以确保在所有权范围之外时释放这块内存。

在这个例子中,Box<dyn Summary> 表示一个实现了 Summary trait 的类型的堆上分配对象的指针,可以指向实现 Summary trait 的任意类型的对象,由 Box 类型进行所有权管理。由于 dyn 表示动态分发,所以当你调用对象的方法时,Rust 在运行时才决定具体调用哪个实现。

**关于Box、Box和 *** :

1: Box<T> 类型可以通过解引用操作符 * 来获取其底层的值 T,但是这只适用于那些在编译时已知大小的类型,例如整数、浮点数、指针等。因为在 Rust 中,解引用操作符会尝试将一个指针类型解引用为其指向的值类型,然后将其复制到一个栈上的变量中,而这个栈上的变量必须具有已知的大小。而在 Rust 中,String 类型和 &str 类型的大小都是在运行时动态分配的,因此不能直接使用解引用操作符来获取它们的值。

2: 在使用 Box<dyn Trait> 时,通常情况下不需要使用解引用操作符 *,因为 Box<T> 类型会自动解引用T 类型。这意味着你可以直接调用 Box<dyn Trait> 实例上的方法,就好像它是实现了 Trait 特征的结构体一样。因此,如果你想要调用 Box<dyn Trait> 实例上的方法,可以直接使用点号操作符来调用,而不需要使用 * 来解引用。( 也可以手动解引用,(*).function() )

关于从Box<dyn Trait> 类型的值转换为具体的实现类型

downcast 方法来尝试将 Box<dyn Trait> 类型的值转换为具体的实现类型,如果成功,则返回一个 Box<T> 类型的值,其中 T 是实现了 Trait 特征的类型。如果转换失败,则返回一个 Err 类型的值。

image-20230220112440501

函数返回特征,通过特征(接口 多态?)可以使用对应的方法。若想将特征转为struct可以使用downcast 。但downcast 方法只能用于将 Any 类型的对象转换为其实际类型,不能用于将 trait object 转换为其实际类型。这是因为 trait object 中不包含实际类型信息,所以无法进行动态类型转换。

常量类型使用downcast

image-20230220131848528

自定义类型使用downcast失败。总有各种各样的错误。采取了直接实现any以及实现any的子类,皆失败。

使用Trait Bound有条件的实现方法

为实现指定特征的结构,提供特定方法。

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

第一个impl块,返回一个新实例的函数Pair<T>。 但是在下一个impl块中,仅当其内部类型实现了 Display 和 PartialOrd 特征时,Pair<T>才可使用此方法。

在动态类型语言中,如果我们在没有定义方法的类型上调用方法,我们会在运行时遇到错误。但是 Rust 将这些错误移至编译时