附录 C:可派生 Trait
在本书的许多地方,我们都讨论了 derive
属性,你可以将其应用于结构体或枚举定义。derive
属性会生成代码,这些代码将在你使用 derive
语法注解的类型上实现具有其自身默认实现的 trait。
在本附录中,我们提供了标准库中所有可以使用 derive
的 trait 的参考。每个部分涵盖:
- 派生此 trait 将启用的运算符和方法
derive
提供的 trait 实现的作用- 实现此 trait 对类型意味着什么
- 允许或不允许实现此 trait 的条件
- 需要此 trait 的操作示例
如果你想要与 derive
属性提供的行为不同的行为,请查阅 标准库文档以获取有关如何手动实现它们的详细信息。
这里列出的 trait 是标准库中定义的唯一可以使用 derive
在你的类型上实现的 trait。标准库中定义的其他 trait 没有合理的默认行为,因此你需要以对你想要完成的任务有意义的方式实现它们。
一个不能被派生的 trait 的例子是 Display
,它处理面向最终用户的格式化。你应该始终考虑向最终用户显示类型的适当方式。最终用户应该被允许看到类型的哪些部分?他们会发现哪些部分是相关的?哪种数据格式对他们来说最相关?Rust 编译器没有这种洞察力,因此它无法为你提供适当的默认行为。
本附录中提供的可派生 trait 列表并非详尽无遗:库可以为自己的 trait 实现 derive
,这使得你可以使用 derive
的 trait 列表真正是开放式的。实现 derive
涉及使用过程宏,这在 “宏”第 20 章的章节中介绍。
Debug
用于程序员输出
Debug
trait 启用格式化字符串中的调试格式化,你可以通过在 {}
占位符中添加 :?
来指示它。
Debug
trait 允许你出于调试目的打印类型的实例,以便你和其他使用你的类型的程序员可以在程序执行的特定点检查实例。
例如,使用 assert_eq!
宏时需要 Debug
trait。如果相等性断言失败,此宏会打印作为参数给出的实例的值,以便程序员可以看到为什么两个实例不相等。
PartialEq
和 Eq
用于相等性比较
PartialEq
trait 允许你比较类型的实例以检查是否相等,并启用 ==
和 !=
运算符的使用。
派生 PartialEq
会实现 eq
方法。当在结构体上派生 PartialEq
时,只有当所有字段都相等时,两个实例才相等;如果任何字段不相等,则实例不相等。当在枚举上派生时,每个变体都等于自身,并且不等于其他变体。
例如,使用 assert_eq!
宏时需要 PartialEq
trait,该宏需要能够比较类型的两个实例是否相等。
Eq
trait 没有方法。它的目的是表明,对于注解类型的每个值,该值都等于自身。Eq
trait 只能应用于也实现了 PartialEq
的类型,尽管并非所有实现了 PartialEq
的类型都可以实现 Eq
。一个例子是浮点数类型:浮点数的实现表明,两个非数字 (NaN
) 值的实例彼此不相等。
需要 Eq
的一个例子是 HashMap<K, V>
中的键,以便 HashMap<K, V>
可以判断两个键是否相同。
PartialOrd
和 Ord
用于排序比较
PartialOrd
trait 允许你比较类型的实例以进行排序。实现了 PartialOrd
的类型可以与 <
, >
, <=
和 >=
运算符一起使用。你只能将 PartialOrd
trait 应用于也实现了 PartialEq
的类型。
派生 PartialOrd
会实现 partial_cmp
方法,当给定的值不产生排序时,该方法会返回 Option<Ordering>
,其值为 None
。一个不产生排序的值的例子,即使该类型的大多数值都可以比较,是非数字 (NaN
) 浮点值。使用任何浮点数和 NaN
浮点值调用 partial_cmp
将返回 None
。
当在结构体上派生时,PartialOrd
通过比较每个字段的值(按照字段在结构体定义中出现的顺序)来比较两个实例。当在枚举上派生时,在枚举定义中较早声明的枚举变体被认为小于稍后列出的变体。
例如,PartialOrd
trait 是 rand
crate 中的 gen_range
方法所必需的,该方法在范围表达式指定的范围内生成一个随机值。
Ord
trait 允许你了解,对于注解类型的任何两个值,都将存在有效的排序。Ord
trait 实现了 cmp
方法,该方法返回 Ordering
而不是 Option<Ordering>
,因为始终可以进行有效排序。你只能将 Ord
trait 应用于也实现了 PartialOrd
和 Eq
的类型(并且 Eq
需要 PartialEq
)。当在结构体和枚举上派生时,cmp
的行为与 PartialOrd
的 partial_cmp
的派生实现方式相同。
需要 Ord
的一个例子是在 BTreeSet<T>
中存储值,这是一种根据值的排序顺序存储数据的数据结构。
Clone
和 Copy
用于复制值
Clone
trait 允许你显式地创建值的深拷贝,并且复制过程可能涉及运行任意代码和复制堆数据。有关 Clone
的更多信息,请参阅 “变量和数据交互的方式:Clone”第 4 章中的章节。
派生 Clone
会实现 clone
方法,当为整个类型实现时,它会对类型的每个部分调用 clone
。这意味着类型中的所有字段或值也必须实现 Clone
才能派生 Clone
。
需要 Clone
的一个例子是在切片上调用 to_vec
方法时。切片不拥有它包含的类型实例,但是从 to_vec
返回的 vector 需要拥有它的实例,因此 to_vec
会对每个项调用 clone
。因此,存储在切片中的类型必须实现 Clone
。
Copy
trait 允许你仅通过复制堆栈上存储的位来复制值;不需要任何任意代码。有关 Copy
的更多信息,请参阅 “从集合中复制 vs. 移动”第 4 章中的章节。
Copy
trait 没有定义任何方法,以防止程序员重载这些方法并违反假设,即没有运行任何任意代码。这样,所有程序员都可以假设复制值会非常快。
你可以在其所有部分都实现了 Copy
的任何类型上派生 Copy
。实现了 Copy
的类型也必须实现 Clone
,因为实现了 Copy
的类型具有 Clone
的平凡实现,该实现执行与 Copy
相同的任务。
Copy
trait 很少是必需的;实现了 Copy
的类型具有可用的优化,这意味着你不必调用 clone
,这使得代码更简洁。
使用 Copy
可以完成的一切,你也可以使用 Clone
完成,但代码可能会更慢,或者必须在某些地方使用 clone
。
Hash
用于将值映射到固定大小的值
Hash
trait 允许你获取任意大小的类型的实例,并使用哈希函数将该实例映射到固定大小的值。派生 Hash
会实现 hash
方法。hash
方法的派生实现结合了对类型的每个部分调用 hash
的结果,这意味着所有字段或值也必须实现 Hash
才能派生 Hash
。
需要 Hash
的一个例子是在 HashMap<K, V>
中存储键以有效地存储数据。
Default
用于默认值
Default
trait 允许你为类型创建默认值。派生 Default
会实现 default
函数。default
函数的派生实现对类型的每个部分调用 default
函数,这意味着类型中的所有字段或值也必须实现 Default
才能派生 Default
。
Default::default
函数通常与 “使用结构体更新语法从其他实例创建实例” 中讨论的结构体更新语法结合使用。第 5 章中的章节。你可以自定义结构体的几个字段,然后通过使用 ..Default::default()
为其余字段设置和使用默认值。
例如,当你对 Option<T>
实例使用方法 unwrap_or_default
时,需要 Default
trait。如果 Option<T>
是 None
,则方法 unwrap_or_default
将返回 Option<T>
中存储的类型 T
的 Default::default
的结果。