特征:定义共享行为
trait 定义了特定类型具有并可以与之共享的功能 其他类型。我们可以使用 trait 以抽象的方式定义共享行为。我们 可以使用 trait bounds 来指定泛型类型可以是具有 某些行为。
注意: 特征类似于其他 语言,尽管存在一些差异。
定义特征
类型的行为由我们可以在该类型上调用的方法组成。不同 如果我们可以对所有这些类型调用相同的方法,那么类型就会共享相同的行为 类型。trait 定义是一种将方法签名组合在一起的方法,以便 定义实现某些目的所需的一组行为。
例如,假设我们有多个结构体,它们包含各种
文本数量:一个NewsArticle
结构体,该结构体包含以
特定位置和Tweet
最多可以有 280 个字符
带有元数据,指示它是新推文、转推还是回复
到另一条推文。
我们想制作一个名为aggregator
那可以
显示可能存储在NewsArticle
或Tweet
实例。为此,我们需要每种类型的摘要,并且我们将请求该摘要
summary 通过调用summarize
方法。示例 10-12 显示了
公共Summary
trait 来表达这种行为。
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
示例 10-12: ASummary
trait 的 trait 中,该 trait 由
行为summarize
方法
在这里,我们使用trait
keyword 的 Keyword 中,然后是 trait 的名称
哪个是Summary
在这种情况下。我们还将 trait 声明为pub
因此
依赖于这个 crate 的 crate 也可以使用这个特性,正如我们将在
几个例子。在大括号内,我们声明方法签名
,它描述了实现此 trait 的类型的行为,其中在
这种情况是fn summarize(&self) -> String
.
在方法签名之后,而不是在 curly 中提供实现
括号中,我们使用分号。实现此 trait 的每个类型都必须提供
它自己的方法主体的自定义行为。编译器将强制执行
任何具有Summary
trait 将具有summarize
完全使用此签名定义。
一个 trait 的主体中可以有多个方法:列出了方法签名 每行一个,每行以分号结尾。
在 Type 上实现 trait
现在我们已经定义了Summary
trait 的方法、
我们可以在 Media Aggregator 中的类型上实现它。示例 10-13 显示
的Summary
traitNewsArticle
struct 的
标题、作者和位置 创建返回值summarize
.对于Tweet
struct 中,我们定义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:实现Summary
traitNewsArticle
和Tweet
类型
在类型上实现 trait 类似于实现常规方法。这
不同的是,在impl
,我们输入我们想要实现的 trait 名称,
然后使用for
keyword,然后指定我们想要的类型的名称
实现 trait for。在impl
块中,我们将方法签名
特征定义已定义。而不是在每个
签名,我们使用大括号,并在方法主体中填充特定的
行为。
现在,该库已经实现了Summary
trait 开启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
.
其他依赖于aggregator
crate 也可以带来Summary
trait 放入 scope 中实现Summary
在他们自己的类型上。一个限制
请注意,只有当 trait 或
type 和/或 both 都是我们 crate 的本地。例如,我们可以实现 standard
库特征,如Display
在自定义类型(如Tweet
作为我们aggregator
crate 功能,因为Tweet
是我们aggregator
板条箱。我们还可以实施Summary
上Vec<T>
在我们的aggregator
crate 的 traitSummary
是我们aggregator
板条箱。
但是我们不能在外部类型上实现 external trait。例如,我们不能
实现Display
trait 开启Vec<T>
在我们的aggregator
crate,因为Display
和Vec<T>
都在 standard 库 中定义,而不是
本地到我们的aggregator
板条箱。此限制是称为 coherence 的属性的一部分,更具体地说是孤立规则,之所以这样命名,是因为
父类型不存在。此规则确保其他人的代码不能
破解你的代码,反之亦然。如果没有该规则,两个 crate 可以实现
相同的 trait 对应相同的类型,并且 Rust 不知道哪个实现
使用。
默认实施
有时,为部分或全部方法设置默认行为很有用 而不是要求每种类型的所有方法都有实现。 然后,当我们在特定类型上实现 trait 时,我们可以保留或覆盖 每个方法的默认行为。
在示例 10-14 中,我们为summarize
方法Summary
trait 而不是只定义方法签名,就像我们在
示例 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:定义Summary
trait 替换为默认的
实现summarize
方法
要使用默认实现来汇总NewsArticle
我们
指定一个空的impl
block 替换为impl Summary for NewsArticle {}
.
即使我们不再定义summarize
method 开启NewsArticle
直接,我们提供了一个默认实现并指定了NewsArticle
实现Summary
特性。因此,我们仍然可以调用
这summarize
method 在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 可以
提供了很多有用的功能,并且只需要实现者指定
其中的一小部分。例如,我们可以定义Summary
trait 中具有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
在Tweet
struct 和summarize
将调用
定义summarize_author
我们提供的服务。因为我们已经实施了summarize_author
这Summary
trait 为我们提供了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 来定义接受许多不同类型的函数。我们将使用Summary
trait 中,我们在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
参数中,我们指定impl
keyword 和特征名称。此参数接受实现
指定的 trait 中。在 bodynotify
,我们可以调用item
这些来自Summary
trait 的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
和item2
parameters 约束函数,以便值的具体类型
作为 的参数传递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
函数返回一些实现Summary
trait 中,而不命名具体类型。在这种情况下,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_display
method (如果其内部类型)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 标准库。例如,标准库实现了ToString
trait 的Display
特性。这impl
块的外观类似于以下代码:
impl<T: Display> ToString for T {
// --snip--
}
因为标准库具有这种一揽子实现,所以我们可以调用to_string
方法由ToString
trait 的 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/)为准