附录 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 涉及到使用过程宏,这在第 19 章的 “宏”章节中进行了介绍。

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) 值的两个实例彼此不相等。

需要 Eq 的一个示例是 HashMap<K, V> 中的键,以便 HashMap<K, V> 可以判断两个键是否相同。

PartialOrdOrd 用于排序比较

PartialOrd trait 允许你比较类型的实例以进行排序。实现 PartialOrd 的类型可以与 <><=>= 运算符一起使用。你只能将 PartialOrd trait 应用于也实现 PartialEq 的类型。

派生 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>,因为始终可以进行有效的排序。你只能将 Ord trait 应用于也实现 PartialOrdEq 的类型(并且 Eq 需要 PartialEq)。当在结构体和枚举上派生时,cmp 的行为与 partial_cmp 的派生实现对于 PartialOrd 的行为相同。

需要 Ord 的一个示例是将值存储在 BTreeSet<T> 中,这是一种根据值的排序顺序存储数据的数据结构。

CloneCopy 用于复制值

Clone trait 允许你显式创建值的深层副本,并且复制过程可能涉及运行任意代码和复制堆数据。有关 Clone 的更多信息,请参见第 4 章中的 “变量和数据的交互方式:Clone”部分。

派生 Clone 会实现 clone 方法,该方法在为整个类型实现时,会在类型的每个部分上调用 clone。这意味着类型中的所有字段或值也必须实现 Clone 才能派生 Clone

需要 Clone 的一个示例是在切片上调用 to_vec 方法时。切片不拥有它包含的类型实例,但是从 to_vec 返回的 vector 将需要拥有其实例,因此 to_vec 会在每个项目上调用 clone。因此,存储在切片中的类型必须实现 Clone

Copy trait 允许你仅通过复制存储在堆栈上的位来复制值;不需要任意代码。有关 Copy 的更多信息,请参见第 4 章中的 “仅限堆栈数据:复制”部分。

Copy trait 不定义任何方法,以防止程序员重载这些方法并违反不运行任何任意代码的假设。这样,所有程序员都可以假定复制值将非常快。

你可以在其所有部分都实现 Copy 的任何类型上派生 Copy。实现 Copy 的类型还必须实现 Clone,因为实现 Copy 的类型具有 Clone 的简单实现,该实现执行与 Copy 相同的任务。

Copy trait 很少需要;实现 Copy 的类型具有可用的优化,这意味着你不必调用 clone,这使代码更简洁。

你可以使用 Clone 完成 Copy 的所有操作,但是代码可能会更慢或必须在某些地方使用 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 的结果。