開發(fā)環(huán)境
- Windows 10
- Rust 1.70.0
?
- ?VS Code 1.79.2
項(xiàng)目工程
這里繼續(xù)沿用上次工程rust-demo
Traits:定義共同的行為
Trait定義了一個(gè)特定類型所具有的功能,并且可以與其他類型共享。我們可以使用特質(zhì)以抽象的方式來定義共享行為。我們可以使用特質(zhì)的界限來指定一個(gè)通用類型可以是任何具有某些行為的類型。?
注意:traits類似于其他語言中通常稱為接口的特性,盡管有一些不同之處。
定義Trait?
一個(gè)類型的行為包括我們可以在該類型上調(diào)用的方法。如果我們可以在所有這些類型上調(diào)用相同的方法,那么不同的類型就有相同的行為。特質(zhì)定義是一種將方法簽名組合在一起的方式,以定義一組完成某些目的所需的行為。?
例如,假設(shè)我們有多個(gè)結(jié)構(gòu)來保存各種類型和數(shù)量的文本:一個(gè)NewsArticle結(jié)構(gòu),保存在特定地點(diǎn)的新聞報(bào)道;一個(gè)Tweet結(jié)構(gòu),最多可以有280個(gè)字符,還有元數(shù)據(jù),表明它是一個(gè)新的Tweet,一個(gè)轉(zhuǎn)發(fā),或?qū)α硪粋€(gè)Tweet的回復(fù)。
我們想做一個(gè)名為aggregator的媒體聚合庫,可以顯示可能存儲(chǔ)在NewsArticle或Tweet實(shí)例中的數(shù)據(jù)摘要。要做到這一點(diǎn),我們需要每種類型的摘要,我們將通過調(diào)用實(shí)例上的summaryize方法來請(qǐng)求該摘要。示例12顯示了一個(gè)表達(dá)這種行為的公共Summary屬性的定義。
文件名:src/lib.rs?
pub trait Summary {
fn summarize(&self) -> String;
}
示例12:一個(gè)由summaryize方法所提供的行為組成的Summary?Trait
在這里,我們使用 trait 關(guān)鍵字聲明一個(gè) trait,然后是 trait 的名字,在這個(gè)例子中是 Summary。我們還將該特性聲明為pub,這樣依賴于該板條箱的板條箱也可以使用該特性,我們將在幾個(gè)例子中看到。在大括號(hào)內(nèi),我們聲明了描述實(shí)現(xiàn)該特性的類型的行為的方法簽名,在本例中是 fn summarize(&self) -> String。?
在方法簽名之后,我們沒有在大括號(hào)內(nèi)提供一個(gè)實(shí)現(xiàn),而是使用了一個(gè)分號(hào)。每個(gè)實(shí)現(xiàn)trait的類型都必須為該方法的主體提供自己的自定義行為。編譯器會(huì)強(qiáng)制要求任何具有Summary?trait的類型都要用這個(gè)簽名定義summaryize方法。
一個(gè)trait在其主體中可以有多個(gè)方法:方法簽名每行列出一個(gè),每行以分號(hào)結(jié)尾。?
在一個(gè)類型上實(shí)現(xiàn)一個(gè)Trait
現(xiàn)在我們已經(jīng)定義了Summary trait方法的所需簽名,我們可以在我們的媒體聚合器中的類型上實(shí)現(xiàn)它。示例13 顯示了在 NewsArticle 結(jié)構(gòu)上對(duì) Summary 特質(zhì)的實(shí)現(xiàn),它使用標(biāo)題、作者和位置來創(chuàng)建 summaryize 的返回值。對(duì)于Tweet結(jié)構(gòu),我們將summaryize定義為用戶名和整個(gè)tweet的文本,假設(shè)tweet內(nèi)容已經(jīng)被限制在280個(gè)字符以內(nèi)。
?文件名:src/lib.rs
pub struct NewsArticle { // 結(jié)構(gòu)
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle { // 實(shí)現(xiàn)
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)
}
}
示例13:在NewsArticle和Tweet類型上實(shí)現(xiàn)Summary Trait
在一個(gè)類型上實(shí)現(xiàn)特質(zhì)與實(shí)現(xiàn)常規(guī)方法類似。不同的是,在 impl 之后,我們要寫上我們想要實(shí)現(xiàn)的特質(zhì)名稱,然后使用 for 關(guān)鍵字,再指定我們想要實(shí)現(xiàn)特質(zhì)的類型名稱。在 impl 塊中,我們把特質(zhì)定義中的方法簽名放進(jìn)去。我們沒有在每個(gè)簽名后面添加分號(hào),而是使用大括號(hào),并在方法體中填寫我們希望特質(zhì)的方法對(duì)特定類型的具體行為。
現(xiàn)Tweet在庫已經(jīng)在NewsArticle和Tweet上實(shí)現(xiàn)了Summary特性,crate的用戶可以在NewsArticle和的實(shí)例上調(diào)用特性方法,就像我們調(diào)用常規(guī)方法一樣。唯一的區(qū)別是,用戶必須把特質(zhì)和類型都帶入范圍。下面是一個(gè)二進(jìn)制板條箱如何使用我們的聚合器庫板條箱的例子:?
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 特質(zhì)帶入范圍,在自己的類型上實(shí)現(xiàn) Summary。需要注意的一個(gè)限制是,只有當(dāng)特質(zhì)或類型中至少有一個(gè)是我們板條箱的本地類型時(shí),我們才能在一個(gè)類型上實(shí)現(xiàn)特質(zhì)。例如,我們可以在自定義類型(如Tweet)上實(shí)現(xiàn)標(biāo)準(zhǔn)庫特性(如Display),作為我們聚合器板塊功能的一部分,因?yàn)?strong>Tweet這個(gè)類型是我們聚合器板塊的本地。我們也可以在我們的聚合器箱中實(shí)現(xiàn)Vec<T>的Summary,因?yàn)?strong>Summary這個(gè)特性是我們aggregator箱的本地特性。
但是我們不能在外部類型上實(shí)現(xiàn)外部特性。例如,我們不能在我們的aggregator箱中對(duì) Vec<T> 實(shí)現(xiàn) Display 特質(zhì),因?yàn)?Display 和 Vec<T> 都是在標(biāo)準(zhǔn)庫中定義的,并不是我們aggregator箱的本地類型。這個(gè)限制是一個(gè)叫做一致性的屬性的一部分,更確切地說,是孤兒規(guī)則,因?yàn)楦割愋筒淮嬖诙幻_@個(gè)規(guī)則確保其他人的代碼不會(huì)破壞你的代碼,反之亦然。如果沒有這個(gè)規(guī)則,兩個(gè)板塊可以為同一類型實(shí)現(xiàn)相同的特性,而Rust不知道該使用哪個(gè)實(shí)現(xiàn)。
默認(rèn)實(shí)現(xiàn)
有時(shí),為特質(zhì)中的某些或所有方法設(shè)置默認(rèn)行為,而不是要求對(duì)每個(gè)類型的所有方法進(jìn)行實(shí)現(xiàn),是非常有用的。然后,當(dāng)我們在一個(gè)特定類型上實(shí)現(xiàn)特質(zhì)時(shí),我們可以保留或覆蓋每個(gè)方法的默認(rèn)行為。
在示例14中,我們?yōu)?/span>Summary trait的summaryize方法指定了一個(gè)默認(rèn)字符串,而不是像示例12中那樣只定義方法簽名。
?文件名:src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
示例14: 定義一個(gè)帶有summaryize方法的默認(rèn)實(shí)現(xiàn)的Summary特質(zhì)?
為了使用默認(rèn)的實(shí)現(xiàn)來總結(jié)NewsArticle的實(shí)例,指定一個(gè)空的代碼塊:impl Summary for NewsArticle {}。
?盡管我們不再直接定義NewsArticle的summaryize方法,但我們已經(jīng)提供了一個(gè)默認(rèn)的實(shí)現(xiàn),并指定NewsArticle實(shí)現(xiàn)了Summary特性。因此,我們?nèi)匀豢梢栽?strong>NewsArticle的一個(gè)實(shí)例上調(diào)用summaryize方法,就像這樣:
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());
這段代碼打?。?strong>New article available! (Read more...)
創(chuàng)建一個(gè)缺省實(shí)現(xiàn)并不要求我們改變示例13中Summary on Tweet的實(shí)現(xiàn)。原因是覆蓋默認(rèn)實(shí)現(xiàn)的語法與實(shí)現(xiàn)沒有默認(rèn)實(shí)現(xiàn)的特質(zhì)方法的語法相同。
默認(rèn)實(shí)現(xiàn)可以調(diào)用同一特質(zhì)中的其他方法,即使這些其他方法沒有默認(rèn)實(shí)現(xiàn)。通過這種方式,一個(gè)trait可以提供很多有用的功能,而只需要實(shí)現(xiàn)者指定其中的一小部分。例如,我們可以定義Summary trait,使其有一個(gè)需要實(shí)現(xiàn)的summaryize_author方法,然后定義一個(gè)summaryize方法,該方法有一個(gè)調(diào)用summaryize_author方法的默認(rèn)實(shí)現(xiàn):
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
要使用這個(gè)版本的Summary,我們只需要在類型上實(shí)現(xiàn)該特性時(shí)定義summaryize_author:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
?在我們定義了summaryize_author之后,我們可以在Tweet結(jié)構(gòu)的實(shí)例上調(diào)用summaryize,而summaryize的默認(rèn)實(shí)現(xiàn)將調(diào)用我們提供的summaryize_author的定義。因?yàn)槲覀円呀?jīng)實(shí)現(xiàn)了summaryize_author,Summary trait已經(jīng)給了我們summaryize方法的行為,而不需要我們再寫任何代碼。
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());
這段代碼打?。?strong>1 new tweet: (Read more from @horse_ebooks...)
注意,不可能從同一方法的重寫實(shí)現(xiàn)中調(diào)用默認(rèn)實(shí)現(xiàn)。
作為參數(shù)的Traits
現(xiàn)在你知道了如何定義和實(shí)現(xiàn)Trait,我們可以探索如何使用特質(zhì)來定義接受許多不同類型的函數(shù)。我們將在示例13中使用我們在NewsArticle和Tweet類型上實(shí)現(xiàn)的Summary trait來定義一個(gè)通知函數(shù),該函數(shù)在其item參數(shù)上調(diào)用summaryize方法,該參數(shù)是一些實(shí)現(xiàn)了Summary trait的類型。要做到這一點(diǎn),我們使用 impl Trait 語法,像這樣:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
?我們?yōu)?strong>item參數(shù)指定 impl 關(guān)鍵字和trait名稱,而不是具體的類型。這個(gè)參數(shù)接受任何實(shí)現(xiàn)了指定trait的類型。在 notify 的主體中,我們可以調(diào)用 item上來自 Summary 特質(zhì)的任何方法,比如 summaryize。我們可以調(diào)用notify 并傳入NewsArticle或Tweet的任何實(shí)例。用任何其他類型(如String或i32)調(diào)用該函數(shù)的代碼將不會(huì)被編譯,因?yàn)檫@些類型沒有實(shí)現(xiàn)Summary 。
Trait Bound句法
impl Trait語法適用于簡單的情況,但實(shí)際上是一種較長形式的語法糖,被稱為trait bound;它看起來像這樣:?
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
這種較長的形式等同于上一節(jié)的例子,但更加啰嗦。我們將特質(zhì)邊界與通用類型參數(shù)的聲明放在冒號(hào)之后和角括號(hào)之內(nèi)。
impl Trait語法很方便,在簡單的情況下可以使代碼更簡潔,而在其他情況下,更完整的trait bound語法可以表達(dá)更多的復(fù)雜性。例如,我們可以有兩個(gè)參數(shù)來實(shí)現(xiàn)Summary。用 impl Trait 語法這樣做,看起來像這樣:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
如果我們想讓這個(gè)函數(shù)允許 item1 和 item2 有不同的類型(只要兩個(gè)類型都實(shí)現(xiàn)了 Summary),那么使用 impl Trait是合適的。然而,如果我們想強(qiáng)制兩個(gè)參數(shù)具有相同的類型,我們必須使用trait bound,像這樣:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
被指定為item1和item2參數(shù)類型的通用類型T約束了該函數(shù),使得作為item1和item2參數(shù)傳遞的值的具體類型必須是相同的。
用 "+"語法指定多個(gè)Traits?Bound
我們還可以指定一個(gè)以上的trait?bound。假設(shè)我們想讓notify在item上使用顯示格式以及summary:我們在notify的定義中指定item必須同時(shí)實(shí)現(xiàn)Display和summary。我們可以使用 "+"語法來這樣做:
pub fn notify(item: &(impl Summary + Display)) {
+ 語法在通用類型的trait bound上也是有效的:
pub fn notify<T: Summary + Display>(item: &T) {
有了這兩個(gè)trait?bound的指定,notify的主體可以調(diào)用summaryize并使用{}來格式化項(xiàng)目。
帶有where子句的更清晰的Traits?Bound
使用過多的trait?bound也有其弊端。每個(gè)泛型都有自己的trait?bound,所以有多個(gè)泛型參數(shù)的函數(shù)在函數(shù)名和參數(shù)列表之間會(huì)包含很多trait?bound信息,使得函數(shù)簽名難以閱讀。出于這個(gè)原因,Rust在函數(shù)簽名后的where子句中提供了另一種語法來指定trait?bound。所以不要寫成這樣:?
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
?我們可以使用一個(gè)where子句,像這樣的:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
?這個(gè)函數(shù)的簽名不那么雜亂:函數(shù)名、參數(shù)列表和返回類型緊挨著,類似于沒有大量特質(zhì)界限的函數(shù)。
返回實(shí)現(xiàn)Traits的類型
我們也可以在返回位置使用 impl Trait 語法來返回某個(gè)實(shí)現(xiàn)了特質(zhì)的類型的值,如下所示:
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 函數(shù)返回一些實(shí)現(xiàn) Summary 特質(zhì)的類型,而不需要命名具體類型。在這種情況下,returns_summarizable返回一個(gè)Tweet,但調(diào)用此函數(shù)的代碼不需要知道這一點(diǎn)。
在閉包和迭代器的上下文中,只通過它所實(shí)現(xiàn)的特征來指定返回類型的能力特別有用,我們在后續(xù)章中介紹了這一點(diǎn)。閉包和迭代器創(chuàng)建了只有編譯器知道的類型,或者指定了非常長的類型。impl Trait語法可以讓你簡潔地指定一個(gè)函數(shù)返回某個(gè)實(shí)現(xiàn)了Iterator trait的類型,而不需要寫出一個(gè)很長的類型。
然而,你只能在返回單一類型的時(shí)候使用 impl Trait。例如,這段返回NewsArticle或Tweet的代碼,其返回類型被指定為implum Summary,則無法使用:
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,
}
}
}
由于編譯器對(duì)impl Trait語法實(shí)現(xiàn)的限制,返回NewsArticle或Tweet是不允許的。
使用Traits?Bound有條件地實(shí)現(xiàn)方法
?通過使用trait與使用通用類型參數(shù)的impl塊綁定,我們可以為實(shí)現(xiàn)指定trait的類型有條件地實(shí)現(xiàn)方法。例如,示例15 中的類型 Pair<T> 總是實(shí)現(xiàn) new 函數(shù),以返回Pair<T> 的新實(shí)例(記得在前面章的 "定義方法 "一節(jié)中,Self 是植入塊的類型的別名,在這里是 Pair<T> )。但是在下一個(gè)impl塊中,Pair<T> 只有在其內(nèi)部類型 T 實(shí)現(xiàn)了能夠進(jìn)行比較的 PartialOrd 特質(zhì)和能夠進(jìn)行打印的 Display 特質(zhì)的情況下才會(huì)實(shí)現(xiàn) cmp_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);
}
}
}
示例15: 根據(jù)trait bound,有條件地在通用類型上實(shí)現(xiàn)方法
我們也可以對(duì)任何實(shí)現(xiàn)了另一個(gè)trait 的類型有條件地實(shí)現(xiàn)一個(gè)trait。在任何滿足trait bound的類型上實(shí)現(xiàn)trait 被稱為空白實(shí)現(xiàn),在Rust標(biāo)準(zhǔn)庫中被廣泛使用。例如,標(biāo)準(zhǔn)庫在任何實(shí)現(xiàn)了Display特質(zhì)的類型上實(shí)現(xiàn)了ToString特質(zhì)。標(biāo)準(zhǔn)庫中的植入塊看起來類似于這段代碼:?
impl<T: Display> ToString for T {
// --snip--
}
?因?yàn)闃?biāo)準(zhǔn)庫有這種很多的實(shí)現(xiàn),我們可以在任何實(shí)現(xiàn)了Display特質(zhì)的類型上調(diào)用ToString特質(zhì)所定義的to_string方法。例如,我們可以像這樣把整數(shù)變成它們相應(yīng)的字符串值,因?yàn)檎麛?shù)實(shí)現(xiàn)了Display:
let s = 3.to_string();
空白的實(shí)現(xiàn)出現(xiàn)在trait的文檔中的 "實(shí)施者 "部分。文章來源:http://www.zghlxwxcb.cn/news/detail-492798.html
trait和trait bound讓我們在寫代碼時(shí)可以使用通用類型的參數(shù)來減少重復(fù),同時(shí)也可以向編譯器說明我們希望通用類型具有特定的行為。然后,編譯器可以使用特質(zhì)約束信息來檢查我們代碼中使用的所有具體類型是否提供了正確的行為。在動(dòng)態(tài)類型語言中,如果我們在一個(gè)沒有定義方法的類型上調(diào)用一個(gè)方法,我們會(huì)在運(yùn)行時(shí)得到一個(gè)錯(cuò)誤。但是Rust將這些錯(cuò)誤轉(zhuǎn)移到了編譯時(shí),所以我們不得不在代碼運(yùn)行之前就修復(fù)這些問題。此外,我們不必在運(yùn)行時(shí)編寫檢查行為的代碼,因?yàn)槲覀円呀?jīng)在編譯時(shí)檢查過了。這樣做可以提高性能,而不必放棄泛型的靈活性。文章來源地址http://www.zghlxwxcb.cn/news/detail-492798.html
本章重點(diǎn)
- Trait概念
- 定義Trait
- 基于具體類型實(shí)現(xiàn)Trait
- Trait的默認(rèn)實(shí)現(xiàn)
- Trait作為參數(shù)的傳遞方法
- 實(shí)現(xiàn)了Trait的返回類型
到了這里,關(guān)于Rust之泛型、特性和生命期(三):Traits:定義共同的行為的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!