C - Derivable Traits
In various places in the book, we discussed the derive
attribute that is
applied to a struct or enum. This attribute generates code that implements a
trait on the annotated type with a default implementation. In this example, the
#[derive(Debug)]
attribute implements the Debug
trait for the Point
struct:
# #![allow(unused_variables)] #fn main() { #[derive(Debug)] struct Point { x: i32, y: i32, } #}
The code that the compiler generates for the implementation of Debug
is
similar to this code:
# #![allow(unused_variables)] #fn main() { # struct Point { # x: i32, # y: i32, # } # impl ::std::fmt::Debug for Point { fn fmt(&self, __arg_0: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match *self { Point { x: ref __self_0_0, y: ref __self_0_1 } => { let mut builder = __arg_0.debug_struct("Point"); let _ = builder.field("x", &&(*__self_0_0)); let _ = builder.field("y", &&(*__self_0_1)); builder.finish() } } } } #}
The generated code implements sensible default behavior for the Debug
trait’s
fmt
function: a match
expression destructures a Point
instance into its
field values. Then it builds up a string containing the struct’s name and each
field’s name and value. This means we’re able to use debug formatting on a
Point
instance to see what value each field has.
The generated code isn’t particularly easy to read because it’s only for the
compiler to consume, rather than for programmers to read! The derive
attribute and the default implementation of Debug
has saved us all of the
work of writing this code for every struct or enum that we want to be able to
print using debug formatting.
The derive
attribute has default implementations for the following traits
provided by the standard library. If you want different behavior than what the
derive
attribute provides, consult the standard library documentation for
each trait for the details needed for manual implementation of the traits.
Standard Library Traits that Can Be Derived
The following sections list all of the traits in the standard library that can
be used 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
Debug
for Programmer Output
The Debug
trait enables debug formatting in format strings, indicated by
adding :?
within {}
placeholders.
The Debug
trait signifies that instances of a type may be printed by
programmers in order to debug their programs by inspecting an instance of a
type at a particular point in a program’s execution.
An example of when Debug
is required is the assert_eq!
macro, which prints
the values of the instances given as arguments if the equality assertion fails
so that programmers can see why the two instances weren’t equal.
PartialEq
and Eq
for Equality Comparisons
The PartialEq
trait signifies that instances of a type can be compared to
each other for equality, and enables use of the ==
and !=
operators.
Deriving PartialEq
implements the eq
method. When derived on structs, two
instances are equal if all fields are equal, and 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.
An example of when PartialEq
is required is the assert_eq!
macro, which
needs to be able to compare two instances of a type for equality.
The Eq
trait doesn’t have any methods. It only signals 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
. An example of types that
implements PartialEq
but that cannot implement Eq
are floating point number
types: the implementation of floating point numbers says that two instances of
the not-a-number value, NaN
, are not equal to each other.
An example of when Eq
is required is for keys in a HashMap
so that the
HashMap
can tell whether two keys are the same.
PartialOrd
and Ord
for Ordering Comparisons
The PartialOrd
trait signifies that instances of a type can be compared to
each other to see which is larger than the other for sorting purposes. A type
that implements PartialOrd
may be used with the <
, >
, <=
, and >=
operators. The PartialOrd
trait can only be applied to types that also
implement PartialEq
.
Deriving PartialOrd
implements the partial_cmp
method, which returns an
Option<Ordering>
that may be None
if comparing the given values does not
produce an ordering. When derived on structs, two instances of the struct are
compared 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 greater than the variants listed
later.
An example of when PartialOrd
is required is the gen_range
method in the
rand
crate that generates a random value in the range specified by a low
value and a high value.
The Ord
trait signifies that for any two value of the annotated type, a valid
ordering exists. The Ord
trait implements the cmp
method, which returns an
Ordering
rather than an Option<Ordering>
because a valid ordering will
always be possible. The Ord
trait can only be applied 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 signifies there is a way to explicitly create a duplicate of
a value, and the duplication process might involve running arbitrary code.
Deriving Clone
implements the clone
method. When derived, the
implementation of clone
for the whole type calls clone
on each of the parts
of the type, so all of 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 containing instances of some type. The slice doesn’t own the instances
but the vector returned from to_vec
will need to own its instances, so the
implementation of to_vec
calls clone
on each item. Thus, the type stored in
the slice must implement Clone
.
The Copy
trait signifies that a value can be duplicated by only copying bits;
no other code is necessary. The Copy
trait does not define any methods to
prevent programmers from overloading those methods violating the assumption
that no arbitrary code is being run. You can derive Copy
on any type whose
parts all implement Copy
. The Copy
trait can only be applied to types that
also implement Clone
, as a type that implements Copy
has a trivial
implementation of Clone
, doing the same thing as Copy
.
Copy
is rarely required; when types implement Copy
, there are optimizations
that can be applied and the code becomes nicer because you don’t have to call
clone
. Everything possible with Copy
can also be accomplished 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 signifies there is a way to take an instance of a type that
takes up an arbitrary amount of size and map that instance to a value of fixed
size by using a hash function. Deriving Hash
implements the hash
method.
When derived, the implementation of hash
for the whole type combines the
result of calling hash
on each of the parts of the type, so all of the fields
or values in the type must also implement Hash
to derive Hash
.
An example of when Hash
is required is for keys in a HashMap
so that the
HashMap
can store data efficiently.
Default
for Default Values
The Default
trait signifies there is a way to create a default value for a
type. Deriving Default
implements the default
method. When derived, the
implementation of Default
for the whole type calls the default
method on
each of the parts of the type, so all of the fields or values in the type must
also implement Default
to derive Default.
A common use of Default::default
is 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 use the default values for the rest by using ..Default::default()
.
An example of when Default
is required is the unwrap_or_default
method on
Option<T>
instances. If the Option<T>
is None
, the unwrap_or_default
method will return the result of Default::default
for the type T
stored in
the Option<T>
.
Standard Library Traits that Can’t Be Derived
The rest of the traits defined in the standard library can’t be implemented on
your types using derive
. These traits don’t have a sensible default behavior
they could have, so you are required to implement them in the way that makes
sense for what you are trying to accomplish with your code.
An example of a trait that can’t be derived is Display
, which handles
formatting of a type for end users of your programs. You should put thought
into 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 into your application, so you must provide it.
Making Custom Traits Derivable
The above list is not comprehensive, however: libraries can implement derive
for their own types! In this way, the list of traits you can use derive
with
is truly open-ended. Implementing derive
involves using a procedural macro,
which is covered in the next appendix, “Macros.”