# 可衍生的特征

我们在本文档的多个地方讨论了可以应用于结构或枚举定义的 derive 属性。derive 属性会生成代码,该代码将使用其自己的默认实现来实现特征,该特征位于你使用 derive 语法注释的类型上。

在本附录中,我们提供了标准库中所有可用于 derive 的特征的参考。每个部分涵盖:

  • 派生此特征的运算符和方法将启用哪些功能
  • 派生提供的特征实现的作用
  • 实现特征对类型有何意义
  • 允许或不允许实现特征的条件
  • 需要特征的操作示例

如果你想要与 derive 属性提供的行为不同的行为,请查阅每个特征的标准库文档 (opens new window)以获取有关如何手动实现它们的详细信息。

此处列出的特征是标准库中定义的唯一特征,可以使用派生在你的类型上实现。标准库中定义的其他特征没有合理的默认行为,因此你需要以符合你要完成的任务的方式实现它们。

无法派生的特征的一个例子是 Display,它处理最终用户的格式。你应该始终考虑向最终用户显示类型的适当方法。应该允许最终用户查看类型的哪些部分?他们会发现哪些部分相关?哪种格式的数据对他们最相关?Rust 编译器没有这种洞察力,因此它无法为你提供适当的默认行为。

本附录提供的可派生特征列表并不全面:库可以为其自己的特征实现派生,这使得可以使用派生的特征列表真正开放。实现派生涉及使用过程宏,第 19 章的“宏”部分介绍了该内容。

# 针对程序输出进行调试

Debug 特征允许在格式字符串中进行调试格式化,你可以通过在 {} 占位符内添加 :? 来指示。

Debug 特征允许你出于调试目的打印类型的实例,因此你和使用你类型的其他程序员可以在程序执行的特定点检查实例。

例如,在使用 assert_eq! 宏时,Debug 特征是必需的。如果相等断言失败,此宏将打印作为参数给出的实例的值,以便程序员可以了解两个实例不相等的原因。

# PartialEq 和 Eq 用于相等比较

PartialEq 特征允许你比较类型的实例以检查是否相等,并允许使用 == 和 != 运算符。

派生 PartialEq 会实现 eq 方法。当 PartialEq 在结构体上派生时,只有当所有字段都相等时,两个实例才相等,如果任何字段不相等,则实例不相等。当在枚举上派生时,每个变体都等于其自身,而不等于其他变体。

PartialEq 特征是必需的,例如,使用 assert_eq! 宏时,它需要能够比较类型的两个实例是否相等。

Eq 特征没有方法。其目的是表示对于注释类型的每个值,该值都等于其自身。 Eq 特征只能应用于也实现 PartialEq 的类型,尽管并非所有实现 PartialEq 的类型都可以实现 Eq。一个例子是浮点数类型:浮点数的实现表明两个非数字 (NaN) 值实例彼此不相等。

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

# PartialOrd 和 Ord 用于排序比较

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

派生 PartialOrd 会实现 partial_cmp 方法,该方法返回一个 Option<Ordering>,当给定的值不产生排序时,它将为 None。不产生排序的值的一个示例(即使该类型的大多数值都可以比较)是非数字 (NaN) 浮点值。使用任何浮点数和 NaN 浮点值调用 partial_cmp 将返回 None。

当在结构体上派生时,PartialOrd 通过按字段在结构体定义中出现的顺序比较每个字段中的值来比较两个实例。当在枚举上派生时,枚举定义中较早声明的枚举变体被认为小于后面列出的变体。

例如,对于 rand crate 中的 gen_range 方法,PartialOrd 特征是必需的,该方法在范围表达式指定的范围内生成随机值。

Ord 特征允许你知道,对于注释类型的任何两个值,都存在有效排序。 Ord 特征实现了 cmp 方法,该方法返回 Ordering 而不是 Option<Ordering>,因为始终可以实现有效排序。你只能将 Ord 特征应用于同时实现 PartialOrd 和 Eq 的类型(并且 Eq 需要 PartialEq)。当在结构和枚举上派生时,cmp 的行为与 partial_cmp 的派生实现与 PartialOrd 的行为相同。

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

# Clone和Copy以复制值

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

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

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

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

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

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

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

使用 Copy 可以实现的所有功能你也可以使用 Clone 实现,但代码可能会更慢或必须在某些地方使用 clone。

# 为Hash映射一个固定大小的值

Hash 特性允许你获取任意大小类型的实例,并使用哈希函数将该实例映射到固定大小的值。派生 Hash 会实现哈希方法。哈希方法的派生实现结合了对类型的每个部分调用哈希的结果,这意味着所有字段或值也必须实现 Hash 才能派生 Hash。

需要 Hash 的一个例子是将键存储在 HashMap<K, V> 中以高效存储数据。

# Default定义默认值

Default 特征允许你为类型创建默认值。派生 Default 会实现默认函数。默认函数的派生实现会在类型的每个部分上调用默认函数,这意味着类型中的所有字段或值也必须实现 Default 才能派生 Default。

Default::default 函数通常与第 5 章“使用结构更新语法从其他实例创建实例”部分中讨论的结构更新语法结合使用。你可以自定义结构的几个字段,然后使用 ..Default::default() 设置并使用其余字段的默认值。

例如,当你在 Option<T> 实例上使用方法 unwrap_or_default 时,Default 特征是必需的。如果 Option<T> 为 None,则方法 unwrap_or_default 将返回存储在 Option<T> 中的类型 T 的 Default::default 的结果。