特性:定义共享行为
特性:定义共享行为
特性定义了特定类型具有并可以与其他类型共享的功能。我们可以使用特性以抽象的方式定义共享行为。我们可以使用特性边界来指定泛型类型可以是任何具有特定行为的类型。
注意:特性类似于其他语言中通常称为接口的功能,尽管存在一些差异。
定义特性
一个类型的行为包括我们可以对该类型调用的方法。如果我们可以对所有这些类型调用相同的方法,那么不同的类型就共享相同的行为。特性定义是一种将方法签名分组以定义完成某些目的所需的一组行为的方式。
例如,假设我们有多个结构体保存不同类型和数量的文本:一个保存特定位置新闻报道的 NewsArticle
结构体,以及一个最多包含 280 个字符并带有元数据(指示它是新推文、转推还是回复另一推文)的 Tweet
结构体。
我们希望创建一个名为 aggregator
的媒体聚合库,该库可以显示可能存储在 NewsArticle
或 Tweet
实例中的数据摘要。为此,我们需要每种类型的摘要,并通过调用实例上的 summarize
方法请求该摘要。下面是一个定义公共 Summary
特性的示例,该特性表达了这种行为。
文件名:src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
示例 10-12:包含 summarize
方法提供的行为的 Summary
特性
在这里,我们使用 trait
关键字和特性的名称(在本例中为 Summary
)声明一个特性。我们还将该特性声明为 pub
,以便依赖此库的其他库也可以使用此特性,如我们将在一些示例中看到的那样。在花括号内,我们声明描述实现此特性的类型的行为的方法签名,在本例中为 fn summarize(&self) -> String
。在方法签名后,我们使用分号而不是提供在花括号内的实现。每个实现此特性的类型必须为方法的主体提供自己的自定义行为。编译器将强制任何具有 Summary
特性的类型必须精确地定义具有此签名的 summarize
方法。
特性可以在其主体中包含多个方法:方法签名每行一个,每行以分号结尾。
在类型上实现特性
现在我们已经定义了 Summary
特性的方法的所需签名,我们可以在我们的媒体聚合器中实现它们。下面是一个在 NewsArticle
结构体上实现 Summary
特性的示例,它使用标题、作者和位置创建 summarize
的返回值。对于 Tweet
结构体,我们将 summarize
定义为用户名后跟整个推文内容,假设推文内容已限制为 280 个字符。
文件名:src/lib.rs
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:在 NewsArticle
和 Tweet
类型上实现 Summary
特性
在类型上实现特性类似于实现常规方法。不同之处在于,在 impl
后,我们放置要实现的特性名称,然后使用 for
关键字,并指定要为其实现特性的类型名称。在 impl
块内,我们放置特性定义定义的方法签名。我们不在每个签名后添加分号,而是使用花括号并用特定类型的方法体填写特性的特定行为。
现在库已经在 NewsArticle
和 Tweet
上实现了 Summary
特性,库的用户可以像调用常规方法一样调用这些类型的实例上的特性方法。唯一的区别是用户必须将特性和类型引入作用域。下面是一个二进制库如何使用我们的 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
库的库也可以将 Summary
特性引入作用域,以在其自己的类型上实现 Summary
。需要注意的一个限制是,我们只能在本地库中实现特性或类型中的至少一个。例如,我们可以在自定义类型 Tweet
上实现标准库特性 Display
,因为类型 Tweet
是本地库的一部分。我们还可以在本地库中实现 Summary
特性,因为特性 Summary
是本地库的一部分。
但是,我们不能在外部类型上实现外部特性。例如,我们不能在 Vec<T>
上实现 Display
特性,因为 Display
和 Vec<T>
都在标准库中定义,不属于本地库。这种限制是被称为一致性(coherence)属性的一部分,具体来说是孤儿规则(orphan rule),因为父类型不存在。该规则确保其他人的代码不会破坏你的代码,反之亦然。如果没有该规则,两个库可能会为同一类型实现相同的特性,而 Rust 不知道该使用哪个实现。
默认实现
有时,为某些或所有方法提供默认行为而不是要求每种类型都实现所有方法是有用的。当我们在特定类型上实现特性时,我们可以保留或覆盖每个方法的默认行为。
在下面的示例中,我们为 Summary
特性的 summarize
方法指定了默认字符串,而不是只定义方法签名。
文件名:src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
示例 10-14:定义 Summary
特性并提供 summarize
方法的默认实现
要使用默认实现来总结 NewsArticle
实例,我们指定一个空的 impl
块 impl Summary for NewsArticle {}
。
尽管我们不再直接定义 NewsArticle
上的 summarize
方法,但我们提供了一个默认实现并指定 NewsArticle
实现了 Summary
特性。因此,我们仍然可以像这样在 NewsArticle
实例上调用 summarize
方法:
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...)
。
创建默认实现不需要我们更改 Tweet
上 Summary
的实现,因为覆盖默认实现的语法与实现没有默认实现的特性方法的语法相同。
默认实现可以调用特性中的其他方法,即使这些其他方法没有默认实现。这样,特性可以提供很多有用的功能,只需要实现者指定其中的一小部分。例如,我们可以定义 Summary
特性以包含 summarize_author
方法,并要求其实现,然后定义一个默认调用 summarize_author
方法的 summarize
方法:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
要使用此版本的 Summary
,我们只需在实现特性时定义 summarize_author
:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
在定义 summarize_author
后,我们可以在 Tweet
结构体的实例上调用 summarize
,而 summarize
的默认实现将调用我们提供的 summarize_author
的定义。因为我们已经实现了 summarize_author
,所以 Summary
特性为我们提供了 summarize
方法的行为,而无需编写更多代码。
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...)
。
特性作为参数
现在我们可以在 Tweet
实例上调用 summarize
,并为 NewsArticle
实例保留默认实现。
我们可以定义函数以接受特性作为参数。我们可以使用 impl Trait
语法来指定参数必须实现特性。这是一个接受任何实现 Summary
特性的类型作为参数的 notify
函数的示例:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
这个 notify
函数调用传递给它的任何实现 Summary
特性的实例的 summarize
方法。
使用 impl Trait
的代码更加简洁,但 impl Trait
语法在一些复杂的场景下可能不够灵活。在那些情况下,你可以使用带有特性边界的泛型函数。
使用特性边界的泛型函数
在 notify
函数中,我们可以使用泛型 T
来代替 impl Summary
,并指定 T
必须实现 Summary
特性:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
这种语法提供了更多的灵活性,因为它允许我们对泛型类型进行多重特性约束,并在更复杂的上下文中使用特性边界。
pub fn notify<T: Summary + Display>(item: &T) {
println!("Breaking news! {}", item);
}
这个 notify
函数接受任何实现了 Summary
和 Display
特性的类型。
在特性中使用 Self
类型
我们可以在特性中使用 Self
类型来引用实现该特性的类型。这允许我们在特性方法的签名中使用实现该特性的具体类型。以下是一个在特性中使用 Self
类型的示例:
pub trait Pilot {
fn fly(&self);
}
pub trait Wizard {
fn fly(&self);
}
pub struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
pub fn fly(&self) {
println!("*waving arms furiously*");
}
}
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
这个示例中,Human
结构体实现了 Pilot
和 Wizard
特性,以及 Human
自己的 fly
方法。我们可以使用特性名来调用特定的 fly
方法。
通过这些示例,你可以理解 Rust 中的特性如何定义共享行为,并如何在类型上实现这些特性。特性允许我们定义清晰且抽象的接口,并在不同类型之间共享行为。这对于构建模块化和可扩展的代码库非常有用。
在 Rust 中,特性(traits)定义了共享行为,是一种抽象接口,用于指定类型必须实现的方法。特性类似于其他语言中的接口。以下是特性的关键点:
-
定义特性:使用
trait
关键字定义特性,并在特性中声明方法签名。实现特性时,必须为这些方法提供具体实现。 -
在类型上实现特性:使用
impl
关键字为特定类型实现特性。可以在不同类型上实现同一特性,从而共享行为。 -
默认实现:特性可以提供方法的默认实现,类型可以选择使用默认实现或覆盖它。
-
特性作为参数:可以定义接受特性作为参数的函数,使用
impl Trait
语法或泛型约束来实现。 -
特性边界:通过泛型函数和特性边界,指定泛型类型必须实现特定特性。特性边界允许对泛型类型进行更复杂的约束。
-
在特性中使用
Self
:在特性中使用Self
类型,引用实现特性的具体类型,从而允许在特性方法中使用实现类型。
特性是 Rust 中强大的抽象工具,使代码模块化、可扩展,支持多态和接口共享。
以下是如何使用带有特性约束的泛型函数的详细示例和解释:
示例代码
use std::fmt::{self, Display, Formatter};
// 定义 Summary 特性
pub trait Summary {
fn summarize(&self) -> String;
}
// 实现 Summary 特性的结构体 NewsArticle
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)
}
}
// 实现 Display 特性的 NewsArticle
impl Display for NewsArticle {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.summarize())
}
}
// 实现 Summary 特性的结构体 Tweet
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)
}
}
// 实现 Display 特性的 Tweet
impl Display for Tweet {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.summarize())
}
}
// 带有特性约束的泛型函数 notify
pub fn notify<T: Summary + Display>(item: &T) {
println!("Breaking news! {}", item);
}
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.",
),
};
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
notify(&article);
notify(&tweet);
}
解释
-
定义
Summary
特性:- 我们定义了一个名为
Summary
的特性,其中包含一个方法summarize
,该方法返回一个String
类型。
- 我们定义了一个名为
-
定义和实现
NewsArticle
结构体:NewsArticle
结构体包含headline
、location
、author
和content
字段。- 实现了
Summary
特性,summarize
方法返回新闻文章的简短描述。 - 为了使
NewsArticle
可以作为notify
函数的参数,我们还实现了Display
特性,其fmt
方法简单调用summarize
方法。
-
定义和实现
Tweet
结构体:Tweet
结构体包含username
、content
、reply
和retweet
字段。- 实现了
Summary
特性,summarize
方法返回推文的简短描述。 - 同样,我们为
Tweet
实现了Display
特性,其fmt
方法简单调用summarize
方法。
-
定义带有特性约束的泛型函数
notify
:notify
函数使用泛型参数T
,并约束T
必须实现Summary
和Display
特性。这样,notify
函数可以接收任何实现了这两个特性的类型。- 在函数体中,我们调用
println!
宏来打印 "Breaking news!" 以及传入参数的字符串表示。由于T
实现了Display
特性,传入参数会自动调用fmt
方法进行格式化。
-
在
main
函数中使用notify
:- 创建了
NewsArticle
和Tweet
的实例。 - 调用了
notify
函数,并传入NewsArticle
和Tweet
的引用。
- 创建了
输出
运行上述代码后,输出将是:
Breaking news! Penguins win the Stanley Cup Championship!, by Iceburgh (Pittsburgh, PA, USA)
Breaking news! horse_ebooks: of course, as you probably already know, people
这个示例展示了如何定义和实现特性,并如何使用带有特性约束的泛型函数来处理实现了这些特性的不同类型。通过这种方式,我们可以编写更加通用和灵活的代码。