Rust 標(biāo)記Trait酵使,公共詞匯Trait

科學(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
DerefDerefMut 智能指針類型的Trait
Default 具有合理“默認(rèn)值”的類型
AsRefAsMut 用于從另一種類型中借入一種引用類型的轉(zhuǎn)換Trait
BorrowBorrowMut 轉(zhuǎn)換Trait惩嘉,類似 AsRef/AsMut,但能額外保證一致的哈希踢故、排序和相等性
FromInto 用于將一種類型的值轉(zhuǎn)換為另一種類型的轉(zhuǎn)換Trait
TryFromTryInto 用于將一種類型的值轉(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ù)傳遞。你只能通過像 &strBox<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 的所有集合類型(VecHashMap琳钉、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直砂。AsMutAsRef 針對可變引用的對應(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] 時該怎么辦呢?你想要的可能是 StringVec<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)用 Cowto_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ǔ)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刹悴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子攒暇,更是在濱河造成了極大的恐慌土匀,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件形用,死亡現(xiàn)場離奇詭異就轧,居然都是意外死亡证杭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門妒御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躯砰,“玉大人,你說我怎么就攤上這事携丁∽列” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵梦鉴,是天一觀的道長李茫。 經(jīng)常有香客問我,道長肥橙,這世上最難降的妖魔是什么魄宏? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮存筏,結(jié)果婚禮上宠互,老公的妹妹穿的比我還像新娘。我一直安慰自己椭坚,他們只是感情好予跌,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著善茎,像睡著了一般券册。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垂涯,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天烁焙,我揣著相機與錄音,去河邊找鬼耕赘。 笑死骄蝇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的操骡。 我是一名探鬼主播九火,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼当娱!你這毒婦竟也來了吃既?” 一聲冷哼從身側(cè)響起考榨,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤跨细,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后河质,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冀惭,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡震叙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了散休。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媒楼。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戚丸,靈堂內(nèi)的尸體忽然破棺而出划址,到底是詐尸還是另有隱情,我是刑警寧澤限府,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布夺颤,位于F島的核電站,受9級特大地震影響胁勺,放射性物質(zhì)發(fā)生泄漏世澜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一署穗、第九天 我趴在偏房一處隱蔽的房頂上張望寥裂。 院中可真熱鬧,春花似錦案疲、人聲如沸封恰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俭驮。三九已至,卻和暖如春春贸,著一層夾襖步出監(jiān)牢的瞬間混萝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工萍恕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逸嘀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓允粤,卻偏偏與公主長得像崭倘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子类垫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容