trait的基本形式,很简单,但这只是trait的冰山一角。当你开始接触大型代码库中的trait时,将会遇到它的多种形式。种类繁多有助于我们完成复杂问题的建模。下面我们一次介绍其他形式的trait,以便了解何时需要使用它们。
标准trait
这是trait定义的最简单形式。我们它作为trait定义进行了阐述:
trait Foo {fn foo();
}
标准库中的一个示例是 Default 特征,它主要是针对可以使用默认值初始化的类型实现的。
泛型trait
trait也可以是泛型。当用户希望为多种类型实现trait的情况下非常有用:
pub trait From<T> {fn from(T) -> Self;
}
这样的例子是 From<T>,它们允许从某种类型转换为类型 T,反之亦然。例如下例,我们给Number实现了From<i32> trait,这意味着,所有的i32,都可以通过Number::from()函数转化为Number。
#[derive(Debug)]
struct Number {value: i32,
}impl From<i32> for Number {fn from(item: i32) -> Self {Number { value: item }}
}fn main() {let num = Number::from(30);println!("My number is {:?}", num);
}
关联类型trait
trait Contains<A, B> {fn contains(&self, _: &A, _: &B) -> bool; fn first(&self) -> i32; fn last(&self) -> i32;
}//关联类型
trait Contains {type A;type B;fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
这是泛型trait的更好选择,因为它们能够在trait中声明相关类型。它们具有较少的类型签名,其优点在于,在实际的编程中,它们允许用户一次性声明关联类型,并在任何特征方法或函数中使用 Self::A 作为返回类型或参数类型。这消除了类型的冗余声明,与泛型trait的情况类似。关联类型trait的最佳用例之一是 Iterator 特征, 它用于迭代自定义类型的值。 在后边文章, 我们将会深入介探讨一下。
marker trait
在 std::marker 模块中定义的特征被称为marker trait。这种trait不包含任何方法,声明时只是提供特征名称和空的函数体。其实我们之前介绍copy和clone时,就介绍了copy其实就是 marker trait。
除了Copy,标准库中的示例还有Send、 Sync。它们被称为marker trait,因为它们用于简单地将类型标记为属于特定的组群,以获得一定程度的编译期保障。标准库中的两个这样的示例是 Send 和 Sync 特征,它们在适当的时候由语言为大多数类型自动实现,并确定哪些值可以安全地发送和跨线程共享。我们将在后续文章对它们进行详细介绍。
父trait
Rust 没有“继承”,但是你可以将一个 trait 定义为另一个 trait 的超集(即父 trait)。例如:
trait Person {fn name(&self) -> String;
}// Person 是 Student 的父 trait。
// 实现 Student 需要你也 impl 了 Person。
trait Student: Person {fn university(&self) -> String;
}
在上述代码片段中,我们声明了一个trait Student,它依赖于父级trait Person。在 Student的定义中,要求用户在为类型实现 Student特征时必须为 Person trait提供实现。标准库中的这样一个示例是 Copy trait,它要求类型必须实现 Clone trait。
派生trait
通过 #[derive]
,编译器能够提供某些 trait 的基本实现。如果需要更复杂的行为,这些 trait 也可以手动实现。
下面是标准库中一些可以自动派生的 trait:
- 比较 trait: Eq,PartialEq,Ord,PartialOrd
- Clone, 用来从
&T
创建副本T
。 - Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。
- Hash,从
&T
计算哈希值(hash)。 - Default, 创建数据类型的一个空实例。
- Debug,使用
{:?}
formatter 来格式化一个值。