附录 C:可派生的 trait
本书多处讨论了 derive 属性,它可以应用于结构体或枚举定义。derive 属性会为使用 derive 语法标注的类型生成代码,从而实现具有默认实现的 trait。
本附录汇总了标准库中所有可以通过 derive 使用的 trait。每节将介绍:
- 派生该 trait 后可以使用哪些运算符和方法
derive提供的该 trait 实现会执行什么操作- 为类型实现该 trait 代表什么
- 允许或不允许为类型实现该 trait 的条件
- 需要该 trait 的操作示例
如果你希望行为与 derive 属性提供的行为不同,请查阅每个 trait 对应的标准库文档,了解如何手动实现它们。
此处列出的 trait 是标准库中唯一可以通过 derive 为自定义类型实现的 trait。标准库中的其他 trait 没有合理的默认行为,因此需要你根据目标自行实现。
无法派生的 trait 示例之一是 Display,它负责面向最终用户的格式化。你应始终考虑如何恰当地向最终用户展示一种类型:用户应该能够看到类型的哪些部分?哪些部分对他们有用?哪种数据格式最适合他们?Rust 编译器无法了解这些信息,因此无法为你提供合适的默认行为。
本附录列出的可派生 trait 并不全面:库可以为自己的 trait 实现 derive,因此可使用 derive 的 trait 列表实际上没有限制。实现 derive 需要使用过程宏,这部分内容在第 20 章的“自定义 derive 宏”一节中介绍。
Debug:面向程序员的输出
Debug trait 支持在格式字符串中进行调试格式化,方法是在 {} 占位符中添加 :?。
Debug trait 允许你为了调试而打印某种类型的实例,以便你和使用该类型的其他程序员检查程序执行到特定位置时的实例状态。
例如,assert_eq! 宏就需要 Debug trait。如果相等断言失败,该宏会打印作为参数传入的实例值,让程序员能够看出这两个实例为何不相等。
PartialEq 和 Eq:相等比较
PartialEq trait 允许你比较某种类型的实例是否相等,并支持使用 == 和 != 运算符。
派生 PartialEq 会实现 eq 方法。为结构体派生 PartialEq 时,只有所有字段都相等,两个实例才相等;只要任意字段不相等,两个实例就不相等。为枚举派生时,每个变体都与自身相等,并且与其他变体不相等。
例如,assert_eq! 宏需要 PartialEq trait,因为它必须能够比较某种类型的两个实例是否相等。
Eq trait 没有任何方法。它用于表明,对于标注类型的每个值,该值都与自身相等。Eq 只能应用于同时实现了 PartialEq 的类型,但并非所有实现 PartialEq 的类型都能实现 Eq。浮点数类型就是一个例子:浮点数的实现规定,两个非数字(NaN)值的实例彼此不相等。
例如,HashMap<K, V> 的键需要实现 Eq,这样 HashMap<K, V> 才能判断两个键是否相同。
PartialOrd 和 Ord:顺序比较
PartialOrd trait 允许你为了排序而比较某种类型的实例。实现了 PartialOrd 的类型可以使用 <、>、<= 和 >= 运算符。只有同时实现了 PartialEq 的类型才能实现 PartialOrd。
派生 PartialOrd 会实现 partial_cmp 方法,该方法返回 Option<Ordering>;当给定的值无法产生顺序关系时,返回值为 None。例如,浮点数中的 NaN 值无法产生顺序关系,尽管该类型的大多数值都可以比较。对任意浮点数和 NaN 调用 partial_cmp 都会返回 None。
为结构体派生 PartialOrd 时,会按照字段在结构体定义中出现的顺序,逐个比较两个实例对应字段的值。为枚举派生时,在枚举定义中较早声明的变体小于较晚声明的变体。
例如,rand crate 中的 gen_range 方法会根据范围表达式指定的范围生成随机值,因此需要 PartialOrd trait。
Ord trait 表明,标注类型的任意两个值之间都存在有效的顺序关系。Ord trait 实现了 cmp 方法,该方法返回 Ordering 而不是 Option<Ordering>,因为顺序关系始终有效。只有同时实现了 PartialOrd 和 Eq(而 Eq 又要求实现 PartialEq)的类型才能实现 Ord。为结构体和枚举派生时,cmp 的行为与通过 PartialOrd 派生的 partial_cmp 相同。
例如,BTreeSet<T> 会根据值的排序顺序存储数据,因此存入其中的值需要实现 Ord。
Clone 和 Copy:复制值
Clone trait 允许你显式创建值的深拷贝,复制过程可能会运行任意代码并复制堆数据。有关 Clone 的更多信息,请参阅第 4 章“变量与数据交互:克隆”一节。
派生 Clone 会实现 clone 方法;为整个类型实现该方法时,它会对类型的每个组成部分调用 clone。因此,类型中的所有字段或值也必须实现 Clone,才能派生 Clone。
例如,对切片调用 to_vec 方法时就需要 Clone。切片并不拥有其中的类型实例,但 to_vec 返回的 vector 必须拥有这些实例,因此 to_vec 会对每个元素调用 clone。所以,存储在切片中的类型必须实现 Clone。
Copy trait 允许你只复制栈上存储的位来复制值,不需要运行任意代码。有关 Copy 的更多信息,请参阅第 4 章“仅栈数据:Copy”一节。
Copy trait 不定义任何方法,以免程序员重载这些方法并违反“不运行任意代码”的假设。这样,所有程序员都可以认为复制一个值非常快。
如果某个类型的所有组成部分都实现了 Copy,就可以为该类型派生 Copy。实现 Copy 的类型也必须实现 Clone,因为这类类型的 Clone 实现很简单,执行的操作与 Copy 相同。
Copy 很少是必需的;实现了 Copy 的类型可以使用相关优化,因此无需调用 clone,代码也会更简洁。
Clone 可以完成 Copy 能完成的所有事情,但代码可能更慢,或者需要在某些位置调用 clone。
Hash:将值映射为固定大小的值
Hash trait 允许你使用哈希函数,将任意大小类型的实例映射为固定大小的值。派生 Hash 会实现 hash 方法。派生实现会合并对类型各个组成部分调用 hash 的结果,因此类型中的所有字段或值也必须实现 Hash,才能派生 Hash。
例如,为了高效存储数据,HashMap<K, V> 中用作键的类型需要实现 Hash。
Default:默认值
Default trait 允许你为某种类型创建默认值。派生 Default 会实现 default 函数。派生实现会对类型的每个组成部分调用 default 函数,因此类型中的所有字段或值也必须实现 Default,才能派生 Default。
Default::default 函数常与结构体更新语法搭配使用;该语法在第 5 章“使用结构体更新语法从其他实例创建实例”一节中介绍。你可以自定义结构体的几个字段,再使用 ..Default::default() 为其余字段设置并采用默认值。
例如,在 Option<T> 实例上使用 unwrap_or_default 方法时就需要 Default trait。如果 Option<T> 是 None,unwrap_or_default 会返回其中类型 T 的 Default::default 结果。