附录 C: 可派生的特征
在书中的各个地方,我们讨论了derive
属性,你可以将其应用于结构体或枚举定义。derive
属性会生成代码,为使用derive
语法标注的类型实现带有默认实现的特征。
在本附录中,我们提供了标准库中所有可以与derive
一起使用的特质的参考。每个部分涵盖:
- 实现这个特质将启用哪些运算符和方法,输出翻译直接,不添加任何额外的文本。绝对不要添加原始翻译内容中没有的符号或标签。记住,保留所有 HTML 标签和属性,仅翻译内容!
derive
提供的特征实现的作用- 实现该特征对类型的意义
- 允许或不允许实现该特征的条件。
- 需要特征的操作示例,
如果您希望的行为与 derive
属性提供的不同,请查阅 标准库文档 以获取有关如何手动实现每个 trait 的详细信息。
这里列出的这些特质是标准库中唯一可以使用derive
在你的类型上实现的。标准库中定义的其他特质没有合理的默认行为,因此需要你自己以适合你目标的方式实现它们。
一个不能派生的特征示例是Display
,它处理面向用户的格式化。您应该始终考虑以适当的方式向最终用户显示类型。最终用户应该被允许看到类型的哪些部分?他们会觉得哪些部分相关?对他们来说,数据的哪种格式最相关?Rust编译器没有这种洞察力,因此无法为您提供适当的行为。
本附录中提供的可派生特性列表并不全面:
库可以为其自己的特性实现derive
,使得你可以使用derive
的特性列表真正开放。实现derive
涉及使用过程宏,这在第20章的
“宏” 部分中有所介绍。
Debug
用于程序员输出
Debug
特性在格式字符串中启用调试格式,你通过在 {}
占位符内添加 :?
来指示。
Debug
特性允许你为了调试目的打印某个类型的实例,这样你和其他使用你类型的程序员可以在程序执行的某个特定点检查一个实例。
Debug
特性在使用 assert_eq!
宏时是必需的。这个宏在等式断言失败时会打印出作为参数给出的实例的值,以便程序员可以看到为什么这两个实例不相等。
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
通过按结构体定义中字段出现的顺序比较每个字段的值来比较两个实例。当在枚举上派生时,枚举定义中较早声明的变体被认为小于后面列出的变体。
PartialOrd
特性是必需的,例如,对于 rand
库中的 gen_range
方法,该方法生成一个在由范围表达式指定的范围内随机值。
Ord
特性允许你知道对于任何两个标注类型的值,都存在一个有效的排序。Ord
特性实现了 cmp
方法,该方法返回一个 Ordering
而不是一个 Option<Ordering>
,因为总是可以有一个有效的排序。你只能将 Ord
特性应用于也实现了 PartialOrd
和 Eq
的类型(而 Eq
需要 PartialEq
)。当在结构体和枚举上派生时,cmp
的行为与 PartialOrd
的派生实现中的 partial_cmp
相同。
一个需要 Ord
的例子是在 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
的类型具有与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>
中存储的类型 T
的 Default::default
结果。