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的类型的名称。
lib位置,以及Cargo.toml中配置
默认实现
为trait中部分或所有方法设置默认行为,而不是要求每种类型的所有方法都实现默认行为很有用。只需实现非默认的行为。
默认方法不需要在特定结构中实现就可以使用。同时默认方法可在特定结构里重写。
特征作为参数
使用特征来定义接受多种不同类型的函数。
Trait Bound语法
pub fn notify<T: Summary>(item: &T){
println!("Breaking news! {}", item.summarize());
}
单参数:
多参数:
若想限制只能同一结构,可以只是用一个泛型参数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!()
}
返回类型为特征
若想返回属于此特征的单个类型,使用if-else会产生如下错误:if
and else
have incompatible types 原因是:当 switch
为真时返回了一个 NewArticle
类型的值,当 switch
为假时返回了一个 Tweet
类型的值。由于这两个类型都实现了 Summary
trait,因此我们可以将它们统一用 impl Summary
来返回。然而,由于 NewArticle
和 Tweet
的类型不同,导致了 if
和 else
两个分支的返回类型不一致,因此编译器会报错。
正确写法:返回类型改为 Box<dyn Summary>
,其中 dyn
是 Rust 中表示 trait 对象的关键字。这个函数现在可以正常运行,而且它可以在运行时动态选择要返回的类型,这也是 trait 对象的一个优点。
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
类型的值。
函数返回特征,通过特征(接口 多态?)可以使用对应的方法。若想将特征转为struct可以使用downcast
。但downcast
方法只能用于将 Any
类型的对象转换为其实际类型,不能用于将 trait object 转换为其实际类型。这是因为 trait object 中不包含实际类型信息,所以无法进行动态类型转换。
常量类型使用downcast
:
自定义类型使用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 将这些错误移至编译时。