附錄 C:可派生的 trait

appendix-03-derivable-traits.md
commit 426f3e4ec17e539ae9905ba559411169d303a031

在本書的各個部分中,我們討論了可應用於結構體和枚舉定義的 derive 屬性。derive 屬性會在使用 derive 語法標記的類型上生成對應 trait 的默認實現的代碼。

在本附錄中提供了標準庫中所有可以使用 derive 的 trait 的參考。這些部分涉及到:

  • 該 trait 將會派生什麼樣的操作符和方法
  • derive 提供什麼樣的 trait 實現
  • 由什麼來實現類型的 trait
  • 是否允許實現該 trait 的條件
  • 需要 trait 操作的例子

如果你希望不同於 derive 屬性所提供的行為,請查閱 標準庫文件 中每個 trait 的細節以了解如何手動實現它們。

標準庫中定義的其它 trait 不能通過 derive 在類型上實現。這些 trait 不存在有意義的默認行為,所以由你負責以合理的方式實現它們。

一個無法被派生的 trait 的例子是為終端用戶處理格式化的 Display 。你應該時常考慮使用合適的方法來為終端用戶顯示一個類型。終端用戶應該看到類型的什麼部分?他們會找出相關部分嗎?對他們來說最相關的數據格式是什麼樣的?Rust 編譯器沒有這樣的洞察力,因此無法為你提供合適的默認行為。

本附錄所提供的可派生 trait 列表並不全面:庫可以為其自己的 trait 實現 derive,可以使用 derive 的 trait 列表事實上是無限的。實現 derive 涉及到過程宏的應用,這在第十九章的 “宏” 有介紹。

用於程式設計師輸出的 Debug

Debug trait 用於開啟格式化字串中的除錯格式,其通過在 {} 占位符中增加 :? 表明。

Debug trait 允許以除錯目的來列印一個類型的實例,所以使用該類型的程式設計師可以在程序執行的特定時間點觀察其實例。

例如,在使用 assert_eq! 宏時,Debug trait 是必須的。如果等式斷言失敗,這個宏就把給定實例的值作為參數列印出來,如此程式設計師可以看到兩個實例為什麼不相等。

等值比較的 PartialEqEq

PartialEq trait 可以比較一個類型的實例以檢查是否相等,並開啟了 ==!= 運算符的功能。

派生的 PartialEq 實現了 eq 方法。當 PartialEq 在結構體上派生時,只有所有 的欄位都相等時兩個實例才相等,同時只要有任何欄位不相等則兩個實例就不相等。當在枚舉上派生時,每一個成員都和其自身相等,且和其他成員都不相等。

例如,當使用 assert_eq! 宏時,需要比較比較一個類型的兩個實例是否相等,則 PartialEq trait 是必須的。

Eq trait 沒有方法。其作用是表明每一個被標記類型的值等於其自身。Eq trait 只能應用於那些實現了 PartialEq 的類型,但並非所有實現了 PartialEq 的類型都可以實現 Eq。浮點類型就是一個例子:浮點數的實現表明兩個非數位(NaN,not-a-number)值是互不相等的。

例如,對於一個 HashMap<K, V> 中的 key 來說, Eq 是必須的,這樣 HashMap<K, V> 就可以知道兩個 key 是否一樣了。

次序比較的 PartialOrdOrd

PartialOrd trait 可以基於排序的目的而比較一個類型的實例。實現了 PartialOrd 的類型可以使用 <><=>= 操作符。但只能在同時實現了 PartialEq 的類型上使用 PartialOrd

派生 PartialOrd 實現了 partial_cmp 方法,其返回一個 Option<Ordering> ,但當給定值無法產生順序時將返回 None。儘管大多數類型的值都可以比較,但一個無法產生順序的例子是:浮點類型的非數字值。當在浮點數上調用 partial_cmp 時,NaN 的浮點數將返回 None

當在結構體上派生時,PartialOrd 以在結構體定義中欄位出現的順序比較每個欄位的值來比較兩個實例。當在枚舉上派生時,認為在枚舉定義中聲明較早的枚舉變體小於其後的變體。

例如,對於來自於 rand crate 中的 gen_range 方法來說,當在一個大值和小值指定的範圍內生成一個隨機值時,PartialOrd trait 是必須的。

Ord trait 也讓你明白在一個帶註解類型上的任意兩個值存在有效順序。Ord trait 實現了 cmp 方法,它返回一個 Ordering 而不是 Option<Ordering>,因為總存在一個合法的順序。只可以在實現了 PartialOrdEqEq 依賴 PartialEq)的類型上使用 Ord trait 。當在結構體或枚舉上派生時, cmp 和以 PartialOrd 派生實現的 partial_cmp 表現一致。

例如,當在 BTreeSet<T>(一種基於有序值存儲數據的數據結構)上存值時,Ord 是必須的。

複製值的 CloneCopy

Clone trait 可以明確地創建一個值的深拷貝(deep copy),複製過程可能包含任意代碼的執行以及堆上數據的複製。查閱第四章 “變數和數據的交互方式:移動” 以獲取有關 Clone 的更多訊息。

派生 Clone 實現了 clone 方法,其為整個的類型實現時,在類型的每一部分上調用了 clone 方法。這意味著類型中所有欄位或值也必須實現了 Clone,這樣才能夠派生 Clone

例如,當在一個切片(slice)上調用 to_vec 方法時,Clone 是必須的。切片並不擁有其所包含實例的類型,但是從 to_vec 中返回的 vector 需要擁有其實例,因此,to_vec 在每個元素上調用 clone。因此,存儲在切片中的類型必須實現 Clone

Copy trait 允許你透過只拷貝存儲在棧上的位來複製值而不需要額外的代碼。查閱第四章 “只在棧上的數據:拷貝” 的部分來獲取有關 Copy 的更多訊息。

Copy trait 並未定義任何方法來阻止編程人員重寫這些方法或違反不需要執行額外代碼的假設。儘管如此,所有的程式人員可以假設複製(copy)一個值非常快。

可以在類型內部全部實現 Copy trait 的任意類型上派生 Copy。 但只可以在那些同時實現了 Clone 的類型上使用 Copy trait ,因為一個實現了 Copy 的類型也簡單地實現了 Clone,其執行和 Copy 相同的任務。

Copy trait 很少使用;實現 Copy 的類型是可以最佳化的,這意味著你無需調用 clone,這讓代碼更簡潔。

任何使用 Copy 的代碼都可以通過 Clone 實現,但代碼可能會稍慢,或者不得不在代碼中的許多位置上使用 clone

固定大小的值到值映射的 Hash

Hash trait 可以實例化一個任意大小的類型,並且能夠用哈希(hash)函數將該實例映射到一個固定大小的值上。派生 Hash 實現了 hash 方法。hash 方法的派生實現結合了在類型的每部分調用 hash 的結果,這意味著所有的欄位或值也必須實現了 Hash,這樣才能夠派生 Hash

例如,在 HashMap<K, V> 上存儲數據,存放 key 的時候,Hash 是必須的。

預設值的 Default

Default trait 使你創建一個類型的預設值。 派生 Default 實現了 default 函數。default 函數的派生實現調用了類型每部分的 default 函數,這意味著類型中所有的欄位或值也必須實現了 Default,這樣才能夠派生 Default

Default::default 函數通常結合結構體更新語法一起使用,這在第五章的 “使用結構體更新語法從其他實例中創建實例” 部分有討論。可以自訂一個結構體的一小部分欄位而剩餘欄位則使用 ..Default::default() 設置為預設值。

例如,當你在 Option<T> 實例上使用 unwrap_or_default 方法時,Default trait是必須的。如果 Option<T>None的話, unwrap_or_default 方法將返回存儲在 Option<T>T 類型的 Default::default 的結果。