科學(xué)無非就是在自然界的多樣性中尋求統(tǒng)一性(或者更確切地說荐吉,是在我們經(jīng)驗的多樣性中尋求統(tǒng)一性)。用 Coleridge 的話說口渔,詩歌样屠、繪畫、藝術(shù)缺脉,同樣是在多樣性中尋求統(tǒng)一性痪欲。
——Jacob Bronowski
標(biāo)記Trait
這類Trait多用作泛型類型變量的限界,以表達無法以其他方式捕獲的約束條件攻礼。Sized 和 Copy 就屬于這類Trait
公共詞匯Trait
這類Trait不涉及任何編譯器魔術(shù)业踢,你完全可以在自己的代碼中定義其等效Trait。之所以定義它們礁扮,是為了給常見問題制定一些約定俗成的解決方案知举。這對 crate 和模塊之間的公共接口來說特別有價值:通過減少不必要的變體,讓接口更容易理解太伊,也增加了把來自不同 crate 的特性輕易插接在一起的可能性雇锡,而且無須樣板代碼或自定義膠水代碼。
這類Trait包括 Default僚焦、引用借用Trait AsRef锰提、AsMut、Borrow 與 BorrowMut、容錯的轉(zhuǎn)換Trait TryFrom 與 TryInto欲账,以及 ToOwned Trait(對 Clone 的泛化)
實用工具Trait匯總表
Trait | 描述 |
---|---|
Drop |
析構(gòu)器屡江。每當(dāng)丟棄一個值時,Rust 都要自動運行的清理代碼 |
Sized |
具有在編譯期已知的固定大小類型的標(biāo)記Trait赛不,與之相對的是動態(tài)大小類型(如切片) |
Clone |
用來支持克隆值的類型 |
Copy |
可以簡單地通過對包含值的內(nèi)存進行逐字節(jié)復(fù)制以進行克隆的類型的標(biāo)記Trait |
Deref 與 DerefMut
|
智能指針類型的Trait |
Default |
具有合理“默認(rèn)值”的類型 |
AsRef 與 AsMut
|
用于從另一種類型中借入一種引用類型的轉(zhuǎn)換Trait |
Borrow 與 BorrowMut
|
轉(zhuǎn)換Trait惩嘉,類似 AsRef /AsMut ,但能額外保證一致的哈希踢故、排序和相等性 |
From 與 Into
|
用于將一種類型的值轉(zhuǎn)換為另一種類型的轉(zhuǎn)換Trait |
TryFrom 與 TryInto
|
用于將一種類型的值轉(zhuǎn)換為另一種類型的轉(zhuǎn)換Trait文黎,用于可能失敗的轉(zhuǎn)換 |
ToOwned |
用于將引用轉(zhuǎn)換為擁有型值的轉(zhuǎn)換Trait |
Sized
固定大小類型是指其每個值在內(nèi)存中都有相同大小的類型。Rust 中的幾乎所有類型都是固定大小的殿较,比如每個 u64
占用 8 字節(jié)耸峭,每個 (f32, f32, f32)
元組占用 12 字節(jié)。甚至枚舉也是有大小的淋纲,也就是說劳闹,無論實際存在的是哪個變體,枚舉總會占據(jù)足夠的空間來容納其最大的變體洽瞬。盡管 Vec<T>
擁有一個大小可變的堆分配緩沖區(qū)本涕,但 Vec
值本身是指向“緩沖區(qū)、容量和長度”的指針伙窃,因此 Vec<T>
也是一個固定大小類型
所有固定大小類型都實現(xiàn)了 std::marker::Sized
Trait菩颖,該Trait沒有方法或關(guān)聯(lián)類型。Rust 自動為所有適用的類型實現(xiàn)了 std::marker::Sized
Trait为障,你不能自己實現(xiàn)它晦闰。Sized
的唯一用途是作為類型變量的限界:像 T: Sized
這樣的限界要求 T
必須是在編譯期已知的類型。由于 Rust 語言本身會使用這種類型的Trait為具有某些特征的類型打上標(biāo)記鳍怨,因此我們將其稱為標(biāo)記Trait
然而呻右,Rust 也有一些無固定大小類型,它們的值大小不盡相同鞋喇。例如窿冯,字符串切片類型 str
(注意沒有 &
)就是無固定大小的。字符串字面量 "diminutive"
和 "big"
是對占用了 10 字節(jié)和 3 字節(jié)的 str
切片的引用确徙,兩者都展示在圖 13-1 中醒串。像 [T]
(同樣沒有 &
)這樣的數(shù)組切片類型也是無固定大小的,即像 &[u8]
這樣的共享引用可以指向任意大小的 [u8]
切片鄙皇。因為 str
類型和 [T]
類型都表示不定大小的值集芜赌,所以它們是無固定大小類型
Rust 不能將無固定大小的值存儲在變量中或?qū)⑺鼈冏鳛閰?shù)傳遞。你只能通過像 &str
或 Box<dyn Write>
這樣的本身是固定大小的指針來處理它們伴逸。如上圖所示缠沈,指向無固定大小值的指針始終是一個胖指針,寬度為兩個機器字:指向切片的指針帶有切片的長度,Trait對象帶有指向方法實現(xiàn)的虛表的指針
盡管存在一些限制洲愤,但無固定大小類型能讓 Rust 的類型系統(tǒng)工作得更順暢
Clone
std::clone::Clone
Trait適用于可復(fù)制自身的類型颓芭。Clone
定義如下:
trait Clone: Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self) {
*self = source.clone()
}
}
clone
方法應(yīng)該為 self
構(gòu)造一個獨立的副本并返回它。由于此方法的返回類型是 Self
柬赐,并且函數(shù)本來也不可能返回?zé)o固定大小的值亡问,因此 Clone
Trait也是擴展自 Sized
Trait的,進而導(dǎo)致其實現(xiàn)代碼中的 Self
類型被限界成了 Sized
克隆一個值通常還需要為它擁有的任何值分配副本肛宋,因此 clone
無論在時間消耗還是內(nèi)存占用方面都是相當(dāng)昂貴的州藕。例如,克隆 Vec<String>
不僅會復(fù)制此向量酝陈,還會復(fù)制它的每個 String
元素床玻。這就是 Rust 不會自動克隆值,而是要求你進行顯式方法調(diào)用的原因沉帮。像 Rc<T>
和 Arc<T>
這樣的引用計數(shù)指針類型屬于例外锈死,即克隆其中任何一個都只會增加引用計數(shù)并為你返回一個新指針
Copy
對于大多數(shù)類型,賦值時會移動值穆壕,而不是復(fù)制它們待牵。移動值可以更簡單地跟蹤它們所擁有的資源
例外情況:不擁有任何資源的簡單類型可以是 Copy
類型,對這些簡單類型賦值會創(chuàng)建源的副本粱檀,而不會移動值并使源回到未初始化狀態(tài)
如果一個類型實現(xiàn)了 std::marker::Copy
標(biāo)記Trait,那么它就是 Copy
類型漫玄,其定義如下所示:
trait Copy: Clone { }
對于你自己的類型茄蚯,這當(dāng)然很容易實現(xiàn):
impl Copy for MyType { }
但由于 Copy
是一種對語言有著特殊意義的標(biāo)記Trait,因此只有當(dāng)類型需要一個淺層的逐字節(jié)復(fù)制時睦优,Rust 才允許它實現(xiàn) Copy
渗常。擁有任何其他資源(比如堆緩沖區(qū)或操作系統(tǒng)句柄)的類型都無法實現(xiàn) Copy
任何實現(xiàn)了 Drop
Trait的類型都不能是 Copy
類型。Rust 認(rèn)為如果一個類型需要特殊的清理代碼汗盘,那么就必然需要特殊的復(fù)制代碼皱碘,因此不能是 Copy
類型
與 Clone
一樣,可以使用 #[derive(Copy)]
讓 Rust 為你派生出 Copy
實現(xiàn)
在允許一個類型成為 Copy
類型之前務(wù)必慎重考慮隐孽。盡管這樣做能讓該類型更易于使用癌椿,但也對其實現(xiàn)施加了嚴(yán)格的限制。如果復(fù)制的開銷很高菱阵,那么就不適合進行隱式復(fù)制
Default
某些類型具有合理的默認(rèn)值:向量或字符串默認(rèn)為空踢俄、數(shù)值默認(rèn)為 0、Option
默認(rèn)為 None
晴及,等等都办。這樣的類型都可以實現(xiàn) std::default::Default
Trait:
trait Default {
fn default() -> Self;
}
default
方法只會返回一個 Self
類型的新值。為 String
實現(xiàn) Default
的代碼一目了然:
impl Default for String {
fn default() -> String {
String::new()
}
}
Rust 的所有集合類型(Vec
、HashMap
琳钉、BinaryHeap
等)都實現(xiàn)了 Default
势木,其 default
方法會返回一個空集合
Default
的另一個常見用途是為表示大量參數(shù)集合的結(jié)構(gòu)體生成默認(rèn)值,其中大部分參數(shù)通常不用更改
如果類型 T
實現(xiàn)了 Default
歌懒,那么標(biāo)準(zhǔn)庫就會自動為 Rc<T>
啦桌、Arc<T>
、Box<T>
歼培、Cell<T>
震蒋、RefCell<T>
、Cow<T>
躲庄、Mutex<T>
和 RwLock<T>
實現(xiàn) Default
如果一個元組類型的所有元素類型都實現(xiàn)了 Default
查剖,那么該元組類型也同樣會實現(xiàn) Default
,這個元組的默認(rèn)值包含每個元素的默認(rèn)值噪窘。
Rust 不會為結(jié)構(gòu)體類型隱式實現(xiàn) Default
笋庄,但是如果結(jié)構(gòu)體的所有字段都實現(xiàn)了 Default
,則可以使用 #[derive(Default)]
為此結(jié)構(gòu)體自動實現(xiàn) Default
AsRef 與 AsMut
如果一個類型實現(xiàn)了 AsRef<T>
倔监,那么就意味著你可以高效地從中借入 &T
直砂。AsMut
是 AsRef
針對可變引用的對應(yīng)類型。它們的定義如下所示:
trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
trait AsMut<T: ?Sized> {
fn as_mut(&mut self) -> &mut T;
}
例如浩习,Vec<T>
實現(xiàn)了 AsRef<[T]>
静暂,而 String
實現(xiàn)了 AsRef<str>
。還可以把 String
的內(nèi)容借入為字節(jié)數(shù)組谱秽,因此 String
也實現(xiàn)了 AsRef<[u8]>
洽蛀。
AsRef
通常用于讓函數(shù)更靈活地接受其參數(shù)類型。例如疟赊,std::fs::File::open
函數(shù)的聲明如下:
fn open<P: AsRef<Path>>(path: P) -> Result<File>
open
真正想要的是 &Path
郊供,即代表文件系統(tǒng)路徑的類型。有了這個函數(shù)簽名近哟,open
就能接受可以從中借入 &Path
的一切驮审,也就是實現(xiàn)了 AsRef<Path>
的一切
Borrow 與 BorrowMut
std::borrow::Borrow
Trait類似于 AsRef
:如果一個類型實現(xiàn)了 Borrow<T>
,那么它的 borrow
方法就能高效地從自身借入一個 &T
吉执。但是 Borrow
施加了更多限制:只有當(dāng) &T
能通過與它借來的值相同的方式進行哈希和比較時疯淫,此類型才應(yīng)實現(xiàn) Borrow<T>
。(Rust 并不強制執(zhí)行此限制戳玫,它只是記述了此Trait的意圖隅津。)這使得 Borrow
在處理哈希表和樹中的鍵或者處理因為某些原因要進行哈仙蚩纾或比較的值時非常有用
這在區(qū)分對 String
的借用時很重要奢驯,比如 String
實現(xiàn)了 AsRef<str>
估蹄、AsRef<[u8]>
和 AsRef<Path>
颂碧,但這 3 種目標(biāo)類型通常具有不一樣的哈希值。只有 &str
切片才能保證像其等效的 String
一樣進行哈希类浪,因此 String
只實現(xiàn)了 Borrow<str>
载城。
Borrow
的定義與 AsRef
的定義基本相同,只是名稱變了:
trait Borrow<Borrowed: ?Sized> {
fn borrow(&self) -> &Borrowed;
}
Vec<T>
和 [T; N]
實現(xiàn)了 Borrow<[T]>
费就。每個類似字符串的類型都能借入其相應(yīng)的切片類型:String
實現(xiàn)了 Borrow<str>
诉瓦、PathBuf
實現(xiàn)了 Borrow<Path>
,等等力细。標(biāo)準(zhǔn)庫中所有關(guān)聯(lián)集合類型都使用 Borrow
來決定哪些類型可以傳給它們的查找函數(shù)睬澡。
標(biāo)準(zhǔn)庫中包含一個通用實現(xiàn),因此每個類型 T
都可以從自身借用:T: Borrow<T>
眠蚂。這確保了在 HashMap<K, V>
中查找條目時 &K
總是可接受的類型煞聪。
為便于使用,每個 &mut T
類型也都實現(xiàn)了 Borrow<T>
逝慧,它會像往常一樣返回一個共享引用 &T
昔脯。這樣你就可以給集合的查找函數(shù)傳入可變引用,而不必重新借入共享引用笛臣,以模擬 Rust 通常會從可變引用到共享引用進行的隱式轉(zhuǎn)換云稚。
BorrowMut
Trait則類似于針對可變引用的 Borrow
:
trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
fn borrow_mut(&mut self) -> &mut Borrowed;
}
剛才講過的對 Borrow
的要求同樣適用于 BorrowMut
ToOwned
給定一個引用,如果此類型實現(xiàn)了 std::clone::Clone
沈堡,則生成其引用目標(biāo)的擁有型副本的常用方法是調(diào)用 clone
静陈。但是當(dāng)你想克隆一個 &str
或 &[i32]
時該怎么辦呢?你想要的可能是 String
或 Vec<i32>
诞丽,但 Clone
的定義不允許這樣做:根據(jù)定義鲸拥,克隆 &T
必須始終返回 T
類型的值,并且 str
和 [u8]
是無固定大小類型率拒,它們甚至都不是函數(shù)所能返回的類型崩泡。
std::borrow::ToOwned
Trait提供了一種稍微寬松的方式來將引用轉(zhuǎn)換為擁有型的值:
trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
}
與必須精確返回 Self
類型的 clone
不同禁荒,to_owned
可以返回任何能讓你從中借入 &Self
的類型:Owned
類型必須實現(xiàn) Borrow<Self>
猬膨。你可以從 Vec<T>
借入 &[T]
,所以只要 T
實現(xiàn)了 Clone
呛伴,[T]
就能實現(xiàn) ToOwned<Owned=Vec<T>>
勃痴,這樣就可以將切片的元素復(fù)制到向量中了。同樣热康,str
實現(xiàn)了 ToOwned<Owned=String>
沛申,Path
實現(xiàn)了 ToOwned<Owned=PathBuf>
,等等
Borrow 與 ToOwned 的實際運用:謙卑的 Cow
謙卑:指不會主動占有資源姐军,直到確有必要
要想用好 Rust铁材,就必然涉及對所有權(quán)問題的透徹思考尖淘,比如函數(shù)應(yīng)該通過引用還是值接受參數(shù)。通常你可以任選一種方式著觉,讓參數(shù)的類型反映你的決定村生。但在某些情況下,在程序開始運行之前你無法決定是該借用還是該擁有饼丘,std::borrow::Cow
類型(用于“寫入時克隆”趁桃,clone on write 的縮寫)提供了一種兼顧兩者的方式。
std::borrow::Cow
的定義如下所示:
enum Cow<'a, B: ?Sized>
where B: ToOwned
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
Cow<B>
要么借入對 B
的共享引用肄鸽,要么擁有可供借入此類引用的值卫病。由于 Cow
實現(xiàn)了 Deref
,因此你可以像對 B
的共享引用一樣調(diào)用它的方法:如果它是 Owned
典徘,就會借入對擁有值的共享引用蟀苛;如果它是 Borrowed
,就會轉(zhuǎn)讓自己持有的引用烂斋。
還可以通過調(diào)用 Cow
的 to_mut
方法來獲取對 Cow
值的可變引用屹逛,這個方法會返回 &mut B
。如果 Cow
恰好是 Cow::Borrowed
汛骂,那么 to_mut
只需調(diào)用引用的 to_owned
方法來獲取其引用目標(biāo)的副本罕模,將 Cow
更改為 Cow::Owned
,并借入對新創(chuàng)建的這個擁有型值的可變引用即可帘瞭。這就是此類型名稱所指的“寫入時克隆”行為淑掌。
類似地,Cow
還有一個 into_owned
方法蝶念,該方法會在必要時提升對所擁有值的引用并返回此引用抛腕,這會將所有權(quán)轉(zhuǎn)移給調(diào)用者并在此過程中消耗掉 Cow
。
Cow
的一個常見用途是返回靜態(tài)分配的字符串常量或由計算得來的字符串媒殉。假設(shè)你需要將錯誤枚舉轉(zhuǎn)換為錯誤消息担敌。大多數(shù)變體可以用固定字符串來處理,但有些也需要在消息中包含附加數(shù)據(jù)
小結(jié)
Rust實用工具trait就都了解了廷蓉,以目前的代碼練習(xí)以及結(jié)合其他資料全封,這些新的概念Trait應(yīng)該如何更好的應(yīng)用,還需多敲代碼桃犬,在實踐中夯實基礎(chǔ)