特征:定义共享行为
trait 定义了特定类型具有并可以与之共享的功能 其他类型。我们可以使用 trait 以抽象的方式定义共享行为。我们 可以使用 trait bounds 来指定泛型类型可以是具有 某些行为。
注意: 特征类似于其他 语言,尽管存在一些差异。
定义特征
类型的行为由我们可以在该类型上调用的方法组成。不同 如果我们可以对所有这些类型调用相同的方法,那么类型就会共享相同的行为 类型。trait 定义是一种将方法签名组合在一起的方法,以便 定义实现某些目的所需的一组行为。
例如,假设我们有多个结构体,它们包含各种
文本数量:一个结构体,其中包含一个以
特定位置和最多可以有 280 个字符
带有元数据,指示它是新推文、转推还是回复
到另一条推文。NewsArticle
Tweet
我们想制作一个名为
显示可能存储在 OR 实例中的数据的摘要。为此,我们需要每种类型的摘要,并且我们将请求该摘要
summary 通过对实例调用方法。示例 10-12 显示了
表达此行为的公共特征的定义。aggregator
NewsArticle
Tweet
summarize
Summary
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
在这里,我们使用关键字声明一个 trait,然后使用 trait 的名称
在这种情况下。我们还将 trait 声明为:
依赖于这个 crate 的 crate 也可以使用这个特性,正如我们将在
几个例子。在大括号内,我们声明方法签名
,它描述了实现此 trait 的类型的行为,其中在
这种情况是 。trait
Summary
pub
fn summarize(&self) -> String
在方法签名之后,而不是在 curly 中提供实现
括号中,我们使用分号。实现此 trait 的每个类型都必须提供
它自己的方法主体的自定义行为。编译器将强制执行
任何具有 trait 的类型都将完全使用此 signature 定义方法。Summary
summarize
一个 trait 的主体中可以有多个方法:列出了方法签名 每行一个,每行以分号结尾。
在 Type 上实现 trait
现在我们已经定义了 trait 方法的所需签名,
我们可以在 Media Aggregator 中的类型上实现它。示例 10-13 显示
结构体上 trait 的实现,它使用
标题、作者和位置 创建返回值 .对于结构体,我们定义为用户名
后跟推文的整个文本,假设推文内容为
已限制为 280 个字符。Summary
Summary
NewsArticle
summarize
Tweet
summarize
文件名: 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)
}
}
在类型上实现 trait 类似于实现常规方法。这
区别在于,在 之后,我们放置了我们想要实现的 trait 名称,
然后使用关键字,然后指定我们想要的类型的名称
实现 trait for。在区块中,我们将方法签名
特征定义已定义。而不是在每个
签名,我们使用大括号,并在方法主体中填充特定的
行为。impl
for
impl
现在,库已经在 和 上实现了 trait ,crate 的用户可以在 和 的实例上调用 trait 方法,就像我们调用常规方法一样。唯一的
区别在于,用户必须将 trait 引入 scope 中,并且
类型。下面是一个二进制 crate 如何使用我们的库 crate 的示例:Summary
NewsArticle
Tweet
NewsArticle
Tweet
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
其他依赖于 crate 的 crate 也可以将 trait 引入范围,以便在它们自己的类型上实现。一个限制
请注意,只有当 trait 或
type 和/或 both 都是我们 crate 的本地。例如,我们可以实现 standard
library trait 一样,就像在自定义类型上一样,作为我们 crate 功能的一部分,因为该类型是 crate 的本地类型。我们也可以在我们的 crate 中实现 on,因为这个 trait 是我们的 crate 本地的。aggregator
Summary
Summary
Display
Tweet
aggregator
Tweet
aggregator
Summary
Vec<T>
aggregator
Summary
aggregator
但是我们不能在 external 类型上实现 external traits。例如,我们不能
在我们的 crate 中实现 trait on,因为 and 都在标准库中定义,而不是
本地到我们的板条箱。此限制是称为 coherence 的属性的一部分,更具体地说是孤立规则,之所以这样命名,是因为
父类型不存在。此规则确保其他人的代码不能
破解你的代码,反之亦然。如果没有该规则,两个 crate 可以实现
相同的 trait 对应相同的类型,并且 Rust 不知道哪个实现
使用。Display
Vec<T>
aggregator
Display
Vec<T>
aggregator
默认实施
有时,为部分或全部方法设置默认行为很有用 而不是要求每种类型的所有方法都有实现。 然后,当我们在特定类型上实现 trait 时,我们可以保留或覆盖 每个方法的默认行为。
在示例 10-14 中,我们为 trait 的方法指定了一个默认字符串,而不是像在
示例 10-12.summarize
Summary
文件名: 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)
}
}
要使用默认实现来总结 的实例,我们
使用 指定空块。NewsArticle
impl
impl Summary for NewsArticle {}
即使我们不再直接定义方法,我们也提供了一个默认实现并指定了实现 trait 的 trait。因此,我们仍然可以调用
的方法,如下所示:summarize
NewsArticle
NewsArticle
Summary
summarize
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...)
创建默认实现不需要我们更改任何内容
示例 10-13 中 on 的实现。原因是
覆盖默认实现的语法与语法相同
实现没有默认实现的 trait 方法。Summary
Tweet
默认实现可以调用同一 trait 中的其他方法,即使这些
其他方法没有默认实现。这样,trait 可以
提供了很多有用的功能,并且只需要实现者指定
其中的一小部分。例如,我们可以定义 trait 以具有一个需要其实现的方法,然后定义一个具有调用该方法的默认实现的方法:Summary
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)
}
}
要使用这个版本的 ,我们只需要定义何时在类型上实现 trait:Summary
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)
}
}
定义 后,我们可以调用结构体的实例,而 的默认实现将调用
定义。因为我们已经实现了 ,所以 trait 为我们提供了方法的行为,而无需我们编写更多代码。具体内容如下
如下所示:summarize_author
summarize
Tweet
summarize
summarize_author
summarize_author
Summary
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 来定义接受许多不同类型的函数。我们将使用我们在 和 类型上实现的 trait
示例 10-13 定义一个调用
在其参数上,该参数是实现 trait 的某种类型。为此,我们使用语法,如下所示:Summary
NewsArticle
Tweet
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());
}
我们指定 keyword 和 trait name 不是参数的具体类型。此参数接受实现
指定的 trait 中。在 的主体中,我们可以调用来自 trait 的任何方法,例如 。我们可以调用并传入 或 的任何实例。调用
具有任何其他类型(例如 a 或 an )的函数都不会编译
因为这些类型不实现 .item
impl
notify
item
Summary
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 参数。
语法很方便,使代码更简洁
case 中,而 fuller trait bound 语法可以在其他
例。例如,我们可以有两个实现 .行为
所以语法看起来像这样:impl Trait
Summary
impl Trait
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
如果我们希望这个函数允许并具有不同的类型(只要两种类型都实现 ),那么 using 是合适的。如果
我们希望强制两个参数具有相同的类型,但是,我们必须使用
trait 绑定,如下所示:impl Trait
item1
item2
Summary
pub fn notify<T: Summary>(item1: &T, item2: &T) {
指定为 和 参数类型的泛型类型约束函数,以便值的具体类型
作为参数传递 for 和 必须相同。T
item1
item2
item1
item2
使用 +
语法指定多个特征边界
我们还可以指定多个 trait bound。假设我们想使用
显示格式以及 ON :我们在定义中指定必须同时实现 和 。我们能做到
所以使用语法: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 后,主体可以调用并使用 format .notify
summarize
{}
item
使用 where
子句的更清晰的 trait Bounds
使用过多的 trait bounds 有其缺点。每个泛型都有自己的 trait
bounds,因此具有多个泛型类型参数的函数可以包含大量
trait 绑定信息,
使函数签名难以阅读。因此,Rust 有替代的
在函数后的子句中指定 trait bounds 的语法
签名。所以,与其这样写: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 的类型
我们还可以在 return 位置使用语法来返回一个
value 实现 trait 的某种类型,如下所示: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)
}
}
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,
}
}
通过使用 for 返回类型,我们指定函数返回实现 trait 的某个类型,而不命名具体类型。在这种情况下, 返回一个 ,但调用此函数的代码不需要知道这一点。impl Summary
returns_summarizable
Summary
returns_summarizable
Tweet
仅通过它实现的 trait 指定返回类型的能力是
在闭包和迭代器的上下文中特别有用,我们将在
第 13 章.闭包和迭代器创建只有编译器知道的类型或
类型。该语法可让您简洁
指定 function 返回实现 trait 的某个类型
而无需写出很长的类型。impl Trait
Iterator
但是,只有在返回单个类型时才能使用。为
示例中,此代码返回 a 或 a 以及
return 类型指定为 WILL 不起作用: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,
}
}
}
由于限制,不允许返回 a 或 a
围绕如何在编译器中实现语法。我们将涵盖
如何在“Using trait Objects that
允许不同的值
Types“部分。NewsArticle
Tweet
impl Trait
使用 trait bounds 有条件地实现方法
通过使用与使用泛型类型参数的块绑定的 trait,
我们可以为实现指定
性状。例如,示例 10-15 中的类型总是实现函数以返回一个新的实例(回想一下第 5 章的“定义方法”部分,它是块类型的类型别名,在本例中为 )。但是在下一个块中,仅当其内部类型实现 trait 时,才实现该方法
,这支持比较,而 trait 则支持打印。impl
Pair<T>
new
Pair<T>
Self
impl
Pair<T>
impl
Pair<T>
cmp_display
T
PartialOrd
Display
文件名: 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);
}
}
}
我们还可以有条件地为任何实现
另一个特征。在满足 trait 的任何类型的 trait 上的实现
bounds 称为 blanket implementations,在
Rust 标准库。例如,标准库在实现 trait 的任何类型上实现 trait。标准库中的块类似于以下代码:ToString
Display
impl
impl<T: Display> ToString for T {
// --snip--
}
因为标准库有这个 blanket 实现,所以我们可以在任何实现
性状。例如,我们可以像这样将整数转换为它们相应的值,因为整数实现了 :to_string
ToString
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/)为准