附录 C: 可派生的特征

在书中的各个地方,我们讨论了derive属性,你可以将其应用于结构体或枚举定义。derive属性会生成代码,为使用derive语法标注的类型实现带有默认实现的特征。

在本附录中,我们提供了标准库中所有可以与derive一起使用的特质的参考。每个部分涵盖:

  • 实现这个特质将启用哪些运算符和方法,输出翻译直接,不添加任何额外的文本。绝对不要添加原始翻译内容中没有的符号或标签。记住,保留所有 HTML 标签和属性,仅翻译内容!
  • derive 提供的特征实现的作用
  • 实现该特征对类型的意义
  • 允许或不允许实现该特征的条件。
  • 需要特征的操作示例,

如果您希望的行为与 derive 属性提供的不同,请查阅 标准库文档 以获取有关如何手动实现每个 trait 的详细信息。

这里列出的这些特质是标准库中唯一可以使用derive在你的类型上实现的。标准库中定义的其他特质没有合理的默认行为,因此需要你自己以适合你目标的方式实现它们。

一个不能派生的特征示例是Display,它处理面向用户的格式化。您应该始终考虑以适当的方式向最终用户显示类型。最终用户应该被允许看到类型的哪些部分?他们会觉得哪些部分相关?对他们来说,数据的哪种格式最相关?Rust编译器没有这种洞察力,因此无法为您提供适当的行为。

本附录中提供的可派生特性列表并不全面: 库可以为其自己的特性实现derive,使得你可以使用derive的特性列表真正开放。实现derive 涉及使用过程宏,这在第20章的 “宏” 部分中有所介绍。

Debug 用于程序员输出

Debug 特性在格式字符串中启用调试格式,你通过在 {} 占位符内添加 :? 来指示。

Debug 特性允许你为了调试目的打印某个类型的实例,这样你和其他使用你类型的程序员可以在程序执行的某个特定点检查一个实例。

Debug 特性在使用 assert_eq! 宏时是必需的。这个宏在等式断言失败时会打印出作为参数给出的实例的值,以便程序员可以看到为什么这两个实例不相等。

PartialEqEq 用于等式比较

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

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

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

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

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

PartialOrdOrd 用于排序比较

PartialOrd 特性允许你为了排序的目的比较某个类型实例。实现了 PartialOrd 的类型可以使用 <><=>= 运算符。你只能将 PartialOrd 特性应用于也实现了 PartialEq 的类型。

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

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

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

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

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

CloneCopy 用于复制值

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的类型具有与Copy执行相同任务的平凡的Clone实现。

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

您可以使用 Clone 实现 Copy 能做的一切,但代码可能会更慢或需要在某些地方使用 clone

Hash 用于将值映射到固定大小的值

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

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

Default 用于默认值

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

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

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