特征:定义共享行为
trait 定义了特定类型具有并可以与之共享的功能 其他类型。我们可以使用 trait 以抽象的方式定义共享行为。我们 可以使用 trait bounds 来指定泛型类型可以是具有 某些行为。
注意: 特征类似于其他 语言,尽管存在一些差异。
定义特征
类型的行为由我们可以在该类型上调用的方法组成。不同 如果我们可以对所有这些类型调用相同的方法,那么类型就会共享相同的行为 类型。trait 定义是一种将方法签名组合在一起的方法,以便 定义实现某些目的所需的一组行为。
例如,假设我们有多个结构体,它们包含各种
文本数量:一个NewsArticle结构体,该结构体包含以
特定位置和Tweet最多可以有 280 个字符
带有元数据,指示它是新推文、转推还是回复
到另一条推文。
我们想制作一个名为aggregator那可以
显示可能存储在NewsArticle或Tweet实例。为此,我们需要每种类型的摘要,并且我们将请求该摘要
summary 通过调用summarize方法。示例 10-12 显示了
公共Summarytrait 来表达这种行为。
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
示例 10-12: ASummarytrait 的 trait 中,该 trait 由
行为summarize方法
在这里,我们使用traitkeyword 的 Keyword 中,然后是 trait 的名称
哪个是Summary在这种情况下。我们还将 trait 声明为pub因此
依赖于这个 crate 的 crate 也可以使用这个特性,正如我们将在
几个例子。在大括号内,我们声明方法签名
,它描述了实现此 trait 的类型的行为,其中在
这种情况是fn summarize(&self) -> String.
在方法签名之后,而不是在 curly 中提供实现
括号中,我们使用分号。实现此 trait 的每个类型都必须提供
它自己的方法主体的自定义行为。编译器将强制执行
任何具有Summarytrait 将具有summarize完全使用此签名定义。
一个 trait 的主体中可以有多个方法:列出了方法签名 每行一个,每行以分号结尾。
在 Type 上实现 trait
现在我们已经定义了Summarytrait 的方法、
我们可以在 Media Aggregator 中的类型上实现它。示例 10-13 显示
的SummarytraitNewsArticlestruct 的
标题、作者和位置 创建返回值summarize.对于Tweetstruct 中,我们定义summarize作为用户名
后跟推文的整个文本,假设推文内容为
已限制为 280 个字符。
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
示例 10-13:实现SummarytraitNewsArticle和Tweet类型
在类型上实现 trait 类似于实现常规方法。这
不同的是,在impl,我们输入我们想要实现的 trait 名称,
然后使用forkeyword,然后指定我们想要的类型的名称
实现 trait for。在impl块中,我们将方法签名
特征定义已定义。而不是在每个
签名,我们使用大括号,并在方法主体中填充特定的
行为。
现在,该库已经实现了Summarytrait 开启NewsArticle和Tweet中,crate 的用户可以在NewsArticle和Tweet以同样的方式调用 regular 方法。唯一的
区别在于,用户必须将 trait 引入 scope 中,并且
类型。下面是一个二进制 crate 如何使用我们的aggregator库箱:
use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
此代码打印1 new tweet: horse_ebooks: of course, as you probably already know, people.
其他依赖于aggregatorcrate 也可以带来Summarytrait 放入 scope 中实现Summary在他们自己的类型上。一个限制
请注意,只有当 trait 或
type 和/或 both 都是我们 crate 的本地。例如,我们可以实现 standard
库特征,如Display在自定义类型(如Tweet作为我们aggregatorcrate 功能,因为Tweet是我们aggregator板条箱。我们还可以实施Summary上Vec<T>在我们的aggregatorcrate 的 traitSummary是我们aggregator板条箱。
但是我们不能在外部类型上实现 external trait。例如,我们不能
实现Displaytrait 开启Vec<T>在我们的aggregatorcrate,因为Display和Vec<T>都在 standard 库 中定义,而不是
本地到我们的aggregator板条箱。此限制是称为 coherence 的属性的一部分,更具体地说是孤立规则,之所以这样命名,是因为
父类型不存在。此规则确保其他人的代码不能
破解你的代码,反之亦然。如果没有该规则,两个 crate 可以实现
相同的 trait 对应相同的类型,并且 Rust 不知道哪个实现
使用。
默认实施
有时,为部分或全部方法设置默认行为很有用 而不是要求每种类型的所有方法都有实现。 然后,当我们在特定类型上实现 trait 时,我们可以保留或覆盖 每个方法的默认行为。
在示例 10-14 中,我们为summarize方法Summarytrait 而不是只定义方法签名,就像我们在
示例 10-12.
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
示例 10-14:定义Summarytrait 替换为默认的
实现summarize方法
要使用默认实现来汇总NewsArticle我们
指定一个空的implblock 替换为impl Summary for NewsArticle {}.
即使我们不再定义summarizemethod 开启NewsArticle直接,我们提供了一个默认实现并指定了NewsArticle实现Summary特性。因此,我们仍然可以调用
这summarizemethod 在NewsArticle喜欢这个:
use aggregator::{self, NewsArticle, Summary};
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
}
此代码打印New article available! (Read more...).
创建默认实现不需要我们更改任何内容
实施Summary上Tweet在示例 10-13 中。原因是
覆盖默认实现的语法与语法相同
实现没有默认实现的 trait 方法。
默认实现可以调用同一 trait 中的其他方法,即使这些
其他方法没有默认实现。这样,trait 可以
提供了很多有用的功能,并且只需要实现者指定
其中的一小部分。例如,我们可以定义Summarytrait 中具有summarize_author方法,然后定义一个summarize方法,该方法具有调用summarize_author方法:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
要使用此版本的Summary,我们只需要定义summarize_author当我们在一个类型上实现 trait 时:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
定义summarize_author,我们可以调用summarize在Tweetstruct 和summarize将调用
定义summarize_author我们提供的服务。因为我们已经实施了summarize_author这Summarytrait 为我们提供了summarize方法,而无需我们编写更多代码。具体内容如下
如下所示:
use aggregator::{self, Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
此代码打印1 new tweet: (Read more from @horse_ebooks...).
请注意,不能从 覆盖同一方法的实现。
作为参数的特征
现在,您知道如何定义和实施特征,我们可以探索如何使用
trait 来定义接受许多不同类型的函数。我们将使用Summarytrait 中,我们在NewsArticle和Tweet键入
示例 10-13 定义一个notify函数调用summarize方法
在其item参数,该参数是实现Summary特性。为此,我们使用impl Trait语法,如下所示:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
而不是item参数中,我们指定implkeyword 和特征名称。此参数接受实现
指定的 trait 中。在 bodynotify,我们可以调用item这些来自Summarytrait 的summarize.我们可以调用notify并传入NewsArticle或Tweet.调用
函数替换为任何其他类型的String或i32,不会编译
因为这些类型没有实现Summary.
特征绑定语法
这impl Trait语法适用于简单的情况,但实际上是语法
糖是一种较长的形式,称为性状绑定;它看起来像这样:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
这种较长的形式等效于上一节中的示例,但 更冗长。我们使用泛型类型的声明放置 trait bounds parameter 的 Parameter 的 S 参数。
这impl Trait语法很方便,并且使代码更简洁
case 中,而 fuller trait bound 语法可以在其他
例。例如,我们可以有两个参数来实现Summary.行为
因此,使用impl Trait语法如下所示:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
用impl Trait如果我们希望此函数允许item1和item2来具有不同的类型(只要两种类型都实现了Summary).如果
我们希望强制两个参数具有相同的类型,但是,我们必须使用
trait 绑定,如下所示:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
泛型类型T指定为item1和item2parameters 约束函数,以便值的具体类型
作为 的参数传递item1和item2必须相同。
使用语法指定多个特征边界+
我们还可以指定多个 trait bound。说我们想要notify使用
显示格式以及summarize上item:我们在notify定义item必须同时实现Display和Summary.我们能做到
所以使用语法:+
pub fn notify(item: &(impl Summary + Display)) {
该语法也适用于泛型类型的 trait bounds:+
pub fn notify<T: Summary + Display>(item: &T) {
指定两个 trait bounds 后,主体notify可以调用summarize并用于格式化{}item.
更清晰的 trait boundswhere第
使用过多的 trait bounds 有其缺点。每个泛型都有自己的 trait
bounds,因此具有多个泛型类型参数的函数可以包含大量
trait 绑定信息,
使函数签名难以阅读。因此,Rust 有替代的
在where子句
签名。所以,与其这样写:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
我们可以使用where子句,如下所示:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
unimplemented!()
}
这个函数的签名不那么杂乱:函数名称、参数列表、 和 return 类型靠得很近,类似于没有很多 trait 的函数 bounds 的
返回实现 trait 的类型
我们还可以使用impl Trait语法以返回
value 实现 trait 的某种类型,如下所示:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
通过使用impl Summary对于返回类型,我们指定returns_summarizable函数返回一些实现Summarytrait 中,而不命名具体类型。在这种情况下,returns_summarizable返回Tweet,但调用此函数的代码不需要知道这一点。
仅通过它实现的 trait 指定返回类型的能力是
在闭包和迭代器的上下文中特别有用,我们将在
第 13 章.闭包和迭代器创建只有编译器知道的类型或
类型。这impl Trait语法让您简洁
指定函数返回实现Iterator特性
而无需写出很长的类型。
但是,您只能使用impl Trait如果您返回单个类型。为
示例中,此代码返回NewsArticle或Tweet使用
返回类型指定为impl Summary不起作用:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}
返回 aNewsArticle或Tweet由于限制而不允许
围绕impl Trait语法在编译器中实现。我们将涵盖
如何在“Using trait Objects that
允许不同的值
Types“部分。
使用 trait bounds 有条件地实现方法
通过使用与impl使用泛型类型参数的块,
我们可以为实现指定
性状。例如,类型Pair<T>在示例 10-15 中,始终实现new函数返回Pair<T>(回想一下第 5 章的“定义方法”部分,Self是impl块,在本例中为Pair<T>).但在接下来的impl块Pair<T>仅实现cmp_displaymethod (如果其内部类型)T实现PartialOrd特性
,这样就可以进行比较,并且Display支持打印的 trait 进行打印。
文件名: src/lib.rs
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);
}
}
}
示例 10-15:在 依赖于 trait 边界的 generic 类型
我们还可以有条件地为任何实现
另一个特征。在满足 trait 的任何类型的 trait 上的实现
bounds 称为 blanket implementations,在
Rust 标准库。例如,标准库实现了ToStringtrait 的Display特性。这impl块的外观类似于以下代码:
impl<T: Display> ToString for T {
// --snip--
}
因为标准库具有这种一揽子实现,所以我们可以调用to_string方法由ToStringtrait 的 trait
这Display特性。例如,我们可以将整数转换为它们对应的String值,因为整数实现Display:
#![allow(unused)] fn main() { let s = 3.to_string(); }
覆盖实现显示在 trait 的 “Implementors” 部分。
trait 和 trait bounds 让我们编写使用泛型类型参数的代码来 减少重复,但也向编译器指定我们想要泛型 type 具有特定行为。然后,编译器可以使用 trait bound 信息来检查代码中使用的所有具体类型是否都提供了 正确的行为。在动态类型语言中,我们会在 运行时,如果我们在未定义该方法的类型上调用方法。但 Rust 将这些错误移动到编译时,因此我们被迫修复问题 在我们的代码甚至能够运行之前。此外,我们不必编写代码 它会在运行时检查行为,因为我们已经在 Compile 中检查过了 时间。这样做可以提高性能,而不必放弃灵活性 的泛型。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准