ch10-02-traits.md
commit 3c2ca8528c3b92b7d30e73f2e8a1b84b2f68b0c8
trait 告訴 Rust 編譯器某個(gè)特定類型擁有可能與其他類型共享的功能??梢酝ㄟ^(guò) trait 以一種抽象的方式定義共享的行為??梢允褂?nbsp;trait bounds 指定泛型是任何擁有特定行為的類型。
注意:trait 類似于其他語(yǔ)言中的常被稱為 接口(interfaces)的功能,雖然有一些不同。
一個(gè)類型的行為由其可供調(diào)用的方法構(gòu)成。如果可以對(duì)不同類型調(diào)用相同的方法的話,這些類型就可以共享相同的行為了。trait 定義是一種將方法簽名組合起來(lái)的方法,目的是定義一個(gè)實(shí)現(xiàn)某些目的所必需的行為的集合。
例如,這里有多個(gè)存放了不同類型和屬性文本的結(jié)構(gòu)體:結(jié)構(gòu)體 NewsArticle
用于存放發(fā)生于世界各地的新聞故事,而結(jié)構(gòu)體 Tweet
最多只能存放 280 個(gè)字符的內(nèi)容,以及像是否轉(zhuǎn)推或是否是對(duì)推友的回復(fù)這樣的元數(shù)據(jù)。
我們想要?jiǎng)?chuàng)建一個(gè)名為 aggregator
的多媒體聚合庫(kù)用來(lái)顯示可能儲(chǔ)存在 NewsArticle
或 Tweet
實(shí)例中的數(shù)據(jù)的總結(jié)。每一個(gè)結(jié)構(gòu)體都需要的行為是他們是能夠被總結(jié)的,這樣的話就可以調(diào)用實(shí)例的 summarize
方法來(lái)請(qǐng)求總結(jié)。示例 10-12 中展示了一個(gè)表現(xiàn)這個(gè)概念的公有 Summary
trait 的定義:
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String;
}
示例 10-12:Summary
trait 定義,它包含由 summarize
方法提供的行為
這里使用 trait
關(guān)鍵字來(lái)聲明一個(gè) trait,后面是 trait 的名字,在這個(gè)例子中是 Summary
。我們也聲明 trait
為 pub
以便依賴這個(gè) crate 的 crate 也可以使用這個(gè) trait,正如我們見(jiàn)過(guò)的一些示例一樣。在大括號(hào)中聲明描述實(shí)現(xiàn)這個(gè) trait 的類型所需要的行為的方法簽名,在這個(gè)例子中是 fn summarize(&self) -> String
。
在方法簽名后跟分號(hào),而不是在大括號(hào)中提供其實(shí)現(xiàn)。接著每一個(gè)實(shí)現(xiàn)這個(gè) trait 的類型都需要提供其自定義行為的方法體,編譯器也會(huì)確保任何實(shí)現(xiàn) Summary
trait 的類型都擁有與這個(gè)簽名的定義完全一致的 summarize
方法。
trait 體中可以有多個(gè)方法:一行一個(gè)方法簽名且都以分號(hào)結(jié)尾。
現(xiàn)在我們定義了 Summary
trait 的簽名,接著就可以在多媒體聚合庫(kù)中實(shí)現(xiàn)這個(gè)類型了。示例 10-13 中展示了 NewsArticle
結(jié)構(gòu)體上 Summary
trait 的一個(gè)實(shí)現(xiàn),它使用標(biāo)題、作者和創(chuàng)建的位置作為 summarize
的返回值。對(duì)于 Tweet
結(jié)構(gòu)體,我們選擇將 summarize
定義為用戶名后跟推文的全部文本作為返回值,并假設(shè)推文內(nèi)容已經(jīng)被限制為 280 字符以內(nèi)。
文件名: 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
類型上實(shí)現(xiàn) Summary
trait
在類型上實(shí)現(xiàn) trait 類似于實(shí)現(xiàn)與 trait 無(wú)關(guān)的方法。區(qū)別在于 impl
關(guān)鍵字之后,我們提供需要實(shí)現(xiàn) trait 的名稱,接著是 for
和需要實(shí)現(xiàn) trait 的類型的名稱。在 impl
塊中,使用 trait 定義中的方法簽名,不過(guò)不再后跟分號(hào),而是需要在大括號(hào)中編寫函數(shù)體來(lái)為特定類型實(shí)現(xiàn) trait 方法所擁有的行為。
現(xiàn)在庫(kù)在 NewsArticle
和 Tweet
上實(shí)現(xiàn)了Summary
trait,crate 的用戶可以像調(diào)用常規(guī)方法一樣調(diào)用 NewsArticle
和 Tweet
實(shí)例的 trait 方法了。唯一的區(qū)別是 trait 必須和類型一起引入作用域以便使用額外的 trait 方法。這是一個(gè)二進(jìn)制 crate 如何利用 aggregator
庫(kù) crate 的例子:
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());
}
這會(huì)打印出 1 new tweet: horse_ebooks: of course, as you probably already know, people
。
其他依賴 aggregator
crate 的 crate 也可以將 Summary
引入作用域以便為其自己的類型實(shí)現(xiàn)該 trait。實(shí)現(xiàn) trait 時(shí)需要注意的一個(gè)限制是,只有當(dāng)至少一個(gè) trait 或者要實(shí)現(xiàn) trait 的類型位于 crate 的本地作用域時(shí),才能為該類型實(shí)現(xiàn) trait。例如,可以為 aggregator
crate 的自定義類型 Tweet
實(shí)現(xiàn)如標(biāo)準(zhǔn)庫(kù)中的 Display
trait,這是因?yàn)?nbsp;Tweet
類型位于 aggregator
crate 本地的作用域中。類似地,也可以在 aggregator
crate 中為 Vec<T>
實(shí)現(xiàn) Summary
,這是因?yàn)?nbsp;Summary
trait 位于 aggregator
crate 本地作用域中。
但是不能為外部類型實(shí)現(xiàn)外部 trait。例如,不能在 aggregator
crate 中為 Vec<T>
實(shí)現(xiàn) Display
trait。這是因?yàn)?nbsp;Display
和 Vec<T>
都定義于標(biāo)準(zhǔn)庫(kù)中,它們并不位于 aggregator
crate 本地作用域中。這個(gè)限制是被稱為 相干性(coherence) 的程序?qū)傩缘囊徊糠?,或者更具體的說(shuō)是 孤兒規(guī)則(orphan rule),其得名于不存在父類型。這條規(guī)則確保了其他人編寫的代碼不會(huì)破壞你代碼,反之亦然。沒(méi)有這條規(guī)則的話,兩個(gè) crate 可以分別對(duì)相同類型實(shí)現(xiàn)相同的 trait,而 Rust 將無(wú)從得知應(yīng)該使用哪一個(gè)實(shí)現(xiàn)。
有時(shí)為 trait 中的某些或全部方法提供默認(rèn)的行為,而不是在每個(gè)類型的每個(gè)實(shí)現(xiàn)中都定義自己的行為是很有用的。這樣當(dāng)為某個(gè)特定類型實(shí)現(xiàn) trait 時(shí),可以選擇保留或重載每個(gè)方法的默認(rèn)行為。
示例 10-14 中展示了如何為 Summary
trait 的 summarize
方法指定一個(gè)默認(rèn)的字符串值,而不是像示例 10-12 中那樣只是定義方法簽名:
文件名: src/lib.rs
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
示例 10-14:Summary
trait 的定義,帶有一個(gè) summarize
方法的默認(rèn)實(shí)現(xiàn)
如果想要對(duì) NewsArticle
實(shí)例使用這個(gè)默認(rèn)實(shí)現(xiàn),而不是定義一個(gè)自己的實(shí)現(xiàn),則可以通過(guò) impl Summary for NewsArticle {}
指定一個(gè)空的 impl
塊。
雖然我們不再直接為 NewsArticle
定義 summarize
方法了,但是我們提供了一個(gè)默認(rèn)實(shí)現(xiàn)并且指定 NewsArticle
實(shí)現(xiàn) Summary
trait。因此,我們?nèi)匀豢梢詫?duì) NewsArticle
實(shí)例調(diào)用 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());
這段代碼會(huì)打印 New article available! (Read more...)
。
為 summarize
創(chuàng)建默認(rèn)實(shí)現(xiàn)并不要求對(duì)示例 10-13 中 Tweet
上的 Summary
實(shí)現(xiàn)做任何改變。其原因是重載一個(gè)默認(rèn)實(shí)現(xiàn)的語(yǔ)法與實(shí)現(xiàn)沒(méi)有默認(rèn)實(shí)現(xiàn)的 trait 方法的語(yǔ)法一樣。
默認(rèn)實(shí)現(xiàn)允許調(diào)用相同 trait 中的其他方法,哪怕這些方法沒(méi)有默認(rèn)實(shí)現(xiàn)。如此,trait 可以提供很多有用的功能而只需要實(shí)現(xiàn)指定一小部分內(nèi)容。例如,我們可以定義 Summary
trait,使其具有一個(gè)需要實(shí)現(xiàn)的 summarize_author
方法,然后定義一個(gè) summarize
方法,此方法的默認(rèn)實(shí)現(xiàn)調(diào)用 summarize_author
方法:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
為了使用這個(gè)版本的 Summary
,只需在實(shí)現(xiàn) trait 時(shí)定義 summarize_author
即可:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
一旦定義了 summarize_author
,我們就可以對(duì) Tweet
結(jié)構(gòu)體的實(shí)例調(diào)用 summarize
了,而 summarize
的默認(rèn)實(shí)現(xiàn)會(huì)調(diào)用我們提供的 summarize_author
定義。因?yàn)閷?shí)現(xiàn)了 summarize_author
,Summary
trait 就提供了 summarize
方法的功能,且無(wú)需編寫更多的代碼。
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());
這會(huì)打印出 1 new tweet: (Read more from @horse_ebooks...)
。
注意無(wú)法從相同方法的重載實(shí)現(xiàn)中調(diào)用默認(rèn)方法。
知道了如何定義 trait 和在類型上實(shí)現(xiàn)這些 trait 之后,我們可以探索一下如何使用 trait 來(lái)接受多種不同類型的參數(shù)。
例如在示例 10-13 中為 NewsArticle
和 Tweet
類型實(shí)現(xiàn)了 Summary
trait。我們可以定義一個(gè)函數(shù) notify
來(lái)調(diào)用其參數(shù) item
上的 summarize
方法,該參數(shù)是實(shí)現(xiàn)了 Summary
trait 的某種類型。為此可以使用 impl Trait
語(yǔ)法,像這樣:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
對(duì)于 item
參數(shù),我們指定了 impl
關(guān)鍵字和 trait 名稱,而不是具體的類型。該參數(shù)支持任何實(shí)現(xiàn)了指定 trait 的類型。在 notify
函數(shù)體中,可以調(diào)用任何來(lái)自 Summary
trait 的方法,比如 summarize
。我們可以傳遞任何 NewsArticle
或 Tweet
的實(shí)例來(lái)調(diào)用 notify
。任何用其它如 String
或 i32
的類型調(diào)用該函數(shù)的代碼都不能編譯,因?yàn)樗鼈儧](méi)有實(shí)現(xiàn) Summary
。
impl Trait
語(yǔ)法適用于直觀的例子,它實(shí)際上是一種較長(zhǎng)形式語(yǔ)法的語(yǔ)法糖。我們稱為 trait bound,它看起來(lái)像:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
這與之前的例子相同,不過(guò)稍微冗長(zhǎng)了一些。trait bound 與泛型參數(shù)聲明在一起,位于尖括號(hào)中的冒號(hào)后面。
impl Trait
很方便,適用于短小的例子。trait bound 則適用于更復(fù)雜的場(chǎng)景。例如,可以獲取兩個(gè)實(shí)現(xiàn)了 Summary
的參數(shù)。使用 impl Trait
的語(yǔ)法看起來(lái)像這樣:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
這適用于 item1
和 item2
允許是不同類型的情況(只要它們都實(shí)現(xiàn)了 Summary
)。不過(guò)如果你希望強(qiáng)制它們都是相同類型呢?這只有在使用 trait bound 時(shí)才有可能:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
泛型 T
被指定為 item1
和 item2
的參數(shù)限制,如此傳遞給參數(shù) item1
和 item2
值的具體類型必須一致。
如果 notify
需要顯示 item
的格式化形式,同時(shí)也要使用 summarize
方法,那么 item
就需要同時(shí)實(shí)現(xiàn)兩個(gè)不同的 trait:Display
和 Summary
。這可以通過(guò) +
語(yǔ)法實(shí)現(xiàn):
pub fn notify(item: &(impl Summary + Display)) {
+
語(yǔ)法也適用于泛型的 trait bound:
pub fn notify<T: Summary + Display>(item: &T) {
通過(guò)指定這兩個(gè) trait bound,notify
的函數(shù)體可以調(diào)用 summarize
并使用 {}
來(lái)格式化 item
。
然而,使用過(guò)多的 trait bound 也有缺點(diǎn)。每個(gè)泛型有其自己的 trait bound,所以有多個(gè)泛型參數(shù)的函數(shù)在名稱和參數(shù)列表之間會(huì)有很長(zhǎng)的 trait bound 信息,這使得函數(shù)簽名難以閱讀。為此,Rust 有另一個(gè)在函數(shù)簽名之后的 where
從句中指定 trait bound 的語(yǔ)法。所以除了這么寫:
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
{
這個(gè)函數(shù)簽名就顯得不那么雜亂,函數(shù)名、參數(shù)列表和返回值類型都離得很近,看起來(lái)跟沒(méi)有那么多 trait bounds 的函數(shù)很像。
也可以在返回值中使用 impl Trait
語(yǔ)法,來(lái)返回實(shí)現(xiàn)了某個(gè) trait 的類型:
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,
}
}
通過(guò)使用 impl Summary
作為返回值類型,我們指定了 returns_summarizable
函數(shù)返回某個(gè)實(shí)現(xiàn)了 Summary
trait 的類型,但是不確定其具體的類型。在這個(gè)例子中 returns_summarizable
返回了一個(gè) Tweet
,不過(guò)調(diào)用方并不知情。
返回一個(gè)只是指定了需要實(shí)現(xiàn)的 trait 的類型的能力在閉包和迭代器場(chǎng)景十分的有用,第十三章會(huì)介紹它們。閉包和迭代器創(chuàng)建只有編譯器知道的類型,或者是非常非常長(zhǎng)的類型。impl Trait
允許你簡(jiǎn)單的指定函數(shù)返回一個(gè) Iterator
而無(wú)需寫出實(shí)際的冗長(zhǎng)的類型。
不過(guò)這只適用于返回單一類型的情況。例如,這段代碼的返回值類型指定為返回 impl Summary
,但是返回了 NewsArticle
或 Tweet
就行不通:
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,
}
}
}
這里嘗試返回 NewsArticle
或 Tweet
。這不能編譯,因?yàn)?nbsp;impl Trait
工作方式的限制。第十七章的 “為使用不同類型的值而設(shè)計(jì)的 trait 對(duì)象” 部分會(huì)介紹如何編寫這樣一個(gè)函數(shù)。
現(xiàn)在你知道了如何使用泛型參數(shù) trait bound 來(lái)指定所需的行為。讓我們回到實(shí)例 10-5 修復(fù)使用泛型類型參數(shù)的 largest
函數(shù)定義!回顧一下,最后嘗試編譯代碼時(shí)出現(xiàn)的錯(cuò)誤是:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` due to previous error
在 largest
函數(shù)體中我們想要使用大于運(yùn)算符(>
)比較兩個(gè) T
類型的值。這個(gè)運(yùn)算符被定義為標(biāo)準(zhǔn)庫(kù)中 trait std::cmp::PartialOrd
的一個(gè)默認(rèn)方法。所以需要在 T
的 trait bound 中指定 PartialOrd
,這樣 largest
函數(shù)可以用于任何可以比較大小的類型的 slice。因?yàn)?nbsp;PartialOrd
位于 prelude 中所以并不需要手動(dòng)將其引入作用域。將 largest
的簽名修改為如下:
fn largest<T: PartialOrd>(list: &[T]) -> T {
但是如果編譯代碼的話,會(huì)出現(xiàn)一些不同的錯(cuò)誤:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src/main.rs:2:23
|
2 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
error[E0507]: cannot move out of a shared reference
--> src/main.rs:4:18
|
4 | for &item in list {
| ----- ^^^^
| ||
| |data moved here
| |move occurs because `item` has type `T`, which does not implement the `Copy` trait
| help: consider removing the `&`: `item`
Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `chapter10` due to 2 previous errors
錯(cuò)誤的核心是 cannot move out of type [T], a non-copy slice
,對(duì)于非泛型版本的 largest
函數(shù),我們只嘗試了尋找最大的 i32
和 char
。正如第四章 “只在棧上的數(shù)據(jù):拷貝” 部分討論過(guò)的,像 i32
和 char
這樣的類型是已知大小的并可以儲(chǔ)存在棧上,所以他們實(shí)現(xiàn)了 Copy
trait。當(dāng)我們將 largest
函數(shù)改成使用泛型后,現(xiàn)在 list
參數(shù)的類型就有可能是沒(méi)有實(shí)現(xiàn) Copy
trait 的。這意味著我們可能不能將 list[0]
的值移動(dòng)到 largest
變量中,這導(dǎo)致了上面的錯(cuò)誤。
為了只對(duì)實(shí)現(xiàn)了 Copy
的類型調(diào)用這些代碼,可以在 T
的 trait bounds 中增加 Copy
!示例 10-15 中展示了一個(gè)可以編譯的泛型版本的 largest
函數(shù)的完整代碼,只要傳遞給 largest
的 slice 值的類型實(shí)現(xiàn)了 PartialOrd
和 Copy
這兩個(gè) trait,例如 i32
和 char
:
文件名: src/main.rs
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
示例 10-15:一個(gè)可以用于任何實(shí)現(xiàn)了 PartialOrd
和 Copy
trait 的泛型的 largest
函數(shù)
如果并不希望限制 largest
函數(shù)只能用于實(shí)現(xiàn)了 Copy
trait 的類型,我們可以在 T
的 trait bounds 中指定 Clone
而不是 Copy
。并克隆 slice 的每一個(gè)值使得 largest
函數(shù)擁有其所有權(quán)。使用 clone
函數(shù)意味著對(duì)于類似 String
這樣擁有堆上數(shù)據(jù)的類型,會(huì)潛在的分配更多堆上空間,而堆分配在涉及大量數(shù)據(jù)時(shí)可能會(huì)相當(dāng)緩慢。
另一種 largest
的實(shí)現(xiàn)方式是返回在 slice 中 T
值的引用。如果我們將函數(shù)返回值從 T
改為 &T
并改變函數(shù)體使其能夠返回一個(gè)引用,我們將不需要任何 Clone
或 Copy
的 trait bounds 而且也不會(huì)有任何的堆分配。嘗試自己實(shí)現(xiàn)這種替代解決方式吧!如果你無(wú)法擺脫與生命周期有關(guān)的錯(cuò)誤,請(qǐng)繼續(xù)閱讀:接下來(lái)的 “生命周期與引用有效性” 部分會(huì)詳細(xì)的說(shuō)明,不過(guò)生命周期對(duì)于解決這些挑戰(zhàn)來(lái)說(shuō)并不是必須的。
通過(guò)使用帶有 trait bound 的泛型參數(shù)的 impl
塊,可以有條件地只為那些實(shí)現(xiàn)了特定 trait 的類型實(shí)現(xiàn)方法。例如,示例 10-16 中的類型 Pair<T>
總是實(shí)現(xiàn)了 new
方法并返回一個(gè) Pair<T>
的實(shí)例(回憶一下第五章的 "定義方法" 部分,Self
是一個(gè) impl
塊類型的類型別名(type alias),在這里是 Pair<T>
)。不過(guò)在下一個(gè) impl
塊中,只有那些為 T
類型實(shí)現(xiàn)了 PartialOrd
trait (來(lái)允許比較) 和 Display
trait (來(lái)啟用打?。┑?nbsp;Pair<T>
才會(huì)實(shí)現(xiàn) cmp_display
方法:
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-16:根據(jù) trait bound 在泛型上有條件的實(shí)現(xiàn)方法
也可以對(duì)任何實(shí)現(xiàn)了特定 trait 的類型有條件地實(shí)現(xiàn) trait。對(duì)任何滿足特定 trait bound 的類型實(shí)現(xiàn) trait 被稱為 blanket implementations,他們被廣泛的用于 Rust 標(biāo)準(zhǔn)庫(kù)中。例如,標(biāo)準(zhǔn)庫(kù)為任何實(shí)現(xiàn)了 Display
trait 的類型實(shí)現(xiàn)了 ToString
trait。這個(gè) impl
塊看起來(lái)像這樣:
impl<T: Display> ToString for T {
// --snip--
}
因?yàn)闃?biāo)準(zhǔn)庫(kù)有了這些 blanket implementation,我們可以對(duì)任何實(shí)現(xiàn)了 Display
trait 的類型調(diào)用由 ToString
定義的 to_string
方法。例如,可以將整型轉(zhuǎn)換為對(duì)應(yīng)的 String
值,因?yàn)檎蛯?shí)現(xiàn)了 Display
:
let s = 3.to_string();
blanket implementation 會(huì)出現(xiàn)在 trait 文檔的 “Implementers” 部分。
trait 和 trait bound 讓我們使用泛型類型參數(shù)來(lái)減少重復(fù),并仍然能夠向編譯器明確指定泛型類型需要擁有哪些行為。因?yàn)槲覀兿蚓幾g器提供了 trait bound 信息,它就可以檢查代碼中所用到的具體類型是否提供了正確的行為。在動(dòng)態(tài)類型語(yǔ)言中,如果我們嘗試調(diào)用一個(gè)類型并沒(méi)有實(shí)現(xiàn)的方法,會(huì)在運(yùn)行時(shí)出現(xiàn)錯(cuò)誤。Rust 將這些錯(cuò)誤移動(dòng)到了編譯時(shí),甚至在代碼能夠運(yùn)行之前就強(qiáng)迫我們修復(fù)錯(cuò)誤。另外,我們也無(wú)需編寫運(yùn)行時(shí)檢查行為的代碼,因?yàn)樵诰幾g時(shí)就已經(jīng)檢查過(guò)了,這樣相比其他那些不愿放棄泛型靈活性的語(yǔ)言有更好的性能。
這里還有一種泛型,我們一直在使用它甚至都沒(méi)有察覺(jué)它的存在,這就是 生命周期(lifetimes)。不同于其他泛型幫助我們確保類型擁有期望的行為,生命周期則有助于確保引用在我們需要他們的時(shí)候一直有效。讓我們學(xué)習(xí)生命周期是如何做到這些的。
更多建議: