Appendix C: Derivable Traits
In various places in the book, we’ve discussed the derive
attribute, which you can apply to a struct or enum definition. The derive
attribute generates code that will implement a trait with its own default implementation on the type you’ve annotated with the derive
syntax.
In this appendix, we provide a reference of all the traits in the standard library that you can use with derive
. Each section covers:
- What operators and methods deriving this trait will enable
- What the implementation of the trait provided by
derive
does - What implementing the trait signifies about the type
- The conditions in which you’re allowed or not allowed to implement the trait
- Examples of operations that require the trait
If you want different behavior than that provided by the derive
attribute, consult the standard library documentation for each trait for details on how to manually implement them.
The rest of the traits defined in the standard library can’t be implemented on your types using derive
. These traits don’t have sensible default behavior, so it’s up to you to implement them in the way that makes sense for what you’re trying to accomplish.
An example of a trait that can’t be derived is Display
, which handles formatting for end users. You should always consider the appropriate way to display a type to an end user. What parts of the type should an end user be allowed to see? What parts would they find relevant? What format of the data would be most relevant to them? The Rust compiler doesn’t have this insight, so it can’t provide appropriate default behavior for you.
The list of derivable traits provided in this appendix is not comprehensive: libraries can implement derive
for their own traits, making the list of traits you can use derive
with truly open-ended. Implementing derive
involves using a procedural macro, which is covered in Appendix D.
Debug
for Programmer Output
The Debug
trait enables debug formatting in format strings, which you indicate by adding :?
within {}
placeholders.
The Debug
trait allows you to print instances of a type for debugging purposes, so you and other programmers using your type can inspect an instance at a particular point in a program’s execution.
The Debug
trait is required, for example, in use of the assert_eq!
macro. This macro prints the values of instances given as arguments if the equality assertion fails so programmers can see why the two instances weren’t equal.
PartialEq
and Eq
for Equality Comparisons
The PartialEq
trait allows you to compare instances of a type to check for equality and enables use of the ==
and !=
operators.
Deriving PartialEq
implements the eq
method. When PartialEq
is derived on structs, two instances are equal only if all fields are equal, and the instances are not equal if any fields are not equal. When derived on enums, each variant is equal to itself and not equal to the other variants.
The PartialEq
trait is required, for example, with the use of the assert_eq!
macro, which needs to be able to compare two instances of a type for equality.
The Eq
trait has no methods. Its purpose is to signal that for every value of the annotated type, the value is equal to itself. The Eq
trait can only be applied to types that also implement PartialEq
, although not all types that implement PartialEq
can implement Eq
. One example of this is floating point number types: the implementation of floating point numbers states that two instances of the not-a-number (NaN
) value are not equal to each other.
An example of when Eq
is required is for keys in a HashMap<K, V>
so the HashMap<K, V>
can tell whether two keys are the same.
PartialOrd
and Ord
for Ordering Comparisons
The PartialOrd
trait allows you to compare instances of a type for sorting purposes. A type that implements PartialOrd
can be used with the <
, >
, <=
, and >=
operators. You can only apply the PartialOrd
trait to types that also implement PartialEq
.
Deriving PartialOrd
implements the partial_cmp
method, which returns an Option<Ordering>
that will be None
when the values given don’t produce an ordering. An example of a value that doesn’t produce an ordering, even though most values of that type can be compared, is the not-a-number (NaN
) floating point value. Calling partial_cmp
with any floating point number and the NaN
floating point value will return None
.
When derived on structs, PartialOrd
compares two instances by comparing the value in each field in the order in which the fields appear in the struct definition. When derived on enums, variants of the enum declared earlier in the enum definition are considered less than the variants listed later.
The PartialOrd
trait is required, for example, for the gen_range
method from the rand
crate that generates a random value in the range specified by a low value and a high value.
The Ord
trait allows you to know that for any two values of the annotated type, a valid ordering will exist. The Ord
trait implements the cmp
method, which returns an Ordering
rather than an Option<Ordering>
because a valid ordering will always be possible. You can only apply the Ord
trait to types that also implement PartialOrd
and Eq
(and Eq
requires PartialEq
). When derived on structs and enums, cmp
behaves the same way as the derived implementation for partial_cmp
does with PartialOrd
.
An example of when Ord
is required is when storing values in a BTreeSet<T>
, a data structure that stores data based on the sort order of the values.
Clone
and Copy
for Duplicating Values
The Clone
trait allows you to explicitly create a deep copy of a value, and the duplication process might involve running arbitrary code and copying heap data. See the “Ways Variables and Data Interact: Clone” section in Chapter 4 for more information on Clone
.
Deriving Clone
implements the clone
method, which when implemented for the whole type, calls clone
on each of the parts of the type. This means all the fields or values in the type must also implement Clone
to derive Clone
.
An example of when Clone
is required is when calling the to_vec
method on a slice. The slice doesn’t own the type instances it contains, but the vector returned from to_vec
will need to own its instances, so to_vec
calls clone
on each item. Thus, the type stored in the slice must implement Clone
.
The Copy
trait allows you to duplicate a value by only copying bits stored on the stack; no arbitrary code is necessary. See the “Stack-Only Data: Copy” section in Chapter 4 for more information on Copy
.
The Copy
trait doesn’t define any methods to prevent programmers from overloading those methods and violating the assumption that no arbitrary code is being run. That way, all programmers can assume that copying a value will be very fast.
You can derive Copy
on any type whose parts all implement Copy
. You can only apply the Copy
trait to types that also implement Clone
, because a type that implements Copy
has a trivial implementation of Clone
that performs the same task as Copy
.
The Copy
trait is rarely required; types that implement Copy
have optimizations available, meaning you don’t have to call clone
, which makes the code more concise.
Everything possible with Copy
you can also accomplish with Clone
, but the code might be slower or have to use clone
in places.
Hash
for Mapping a Value to a Value of Fixed Size
The Hash
trait allows you to take an instance of a type of arbitrary size and map that instance to a value of fixed size using a hash function. Deriving Hash
implements the hash
method. The derived implementation of the hash
method combines the result of calling hash
on each of the parts of the type, meaning all fields or values must also implement Hash
to derive Hash
.
An example of when Hash
is required is in storing keys in a HashMap<K, V>
to store data efficiently.
Default
for Default Values
The Default
trait allows you to create a default value for a type. Deriving Default
implements the default
function. The derived implementation of the default
function calls the default
function on each part of the type, meaning all fields or values in the type must also implement Default
to derive Default.
The Default::default
function is commonly used in combination with the struct update syntax discussed in the “Creating Instances From Other Instances With Struct Update Syntax” section in Chapter 5. You can customize a few fields of a struct and then set and use a default value for the rest of the fields by using ..Default::default()
.
The Default
trait is required when you use the method unwrap_or_default
on Option<T>
instances, for example. If the Option<T>
is None
, the method unwrap_or_default
will return the result of Default::default
for the type T
stored in the Option<T>
.