📜 ⬆️ ⬇️

Traits out of the box

In the standard library of the Rust language there are several traits that can be declared "for free" using derive . These traits are sure to come in handy when declaring your own structures, they are very often found in various open-source libraries, but their implementation is generated by the compiler and can cause questions.
Often see:

#[derive(RustcEncodable, RustcDecodable, Clone, Eq, Default)] struct Foo { } 

and do not understand what it is and where?

The compiler itself can provide you with simple "embedded" implementations for some traits using #[derive] . Of course, these same traits can be implemented manually if more complex behavior is required.

So, here is an exemplary list of traits that can be “extracted” (derive is translated exactly this way):
')

In addition, unstable:


All of the above traits are in the standard library std . In addition, you can find self-made libraries, where there are also traits that are extracted using #[derive] . An example is the commonly used RustcEncodable / RustcDecodable . You can implement derive support for your traits with the help of very tricky macros (which most likely will not work in half the cases :)), but this is beyond the scope of our article.

Consider the above traits


Comparison traits


Allowed to arrange for their structures model of equivalence relations . Subsequently, structures can be compared, organized, structured and organized. Consider them in more detail.

Eq and PartialEq


Two similar traits are responsible for saying foo == bar or not. But why on such a simple matter two different treit? And the answer is:

PartialEq does not guarantee equality with itself: not the fact that a == a

How can this be? For example, the numbers NaN! = NaN

 use std::f32; fn main() { let a = f32::NAN; let b = f32::NAN; println!("{}", a == b); } 

returns false

Imagine yourself as the creator of a library in which there is some sort of comparison function:

 fn foobar<T>(param: T) { ... } 

How to choose a limit for T ? If complete equivalence is fundamental to us, and without it, the function will collapse, we write this:

 fn foo<T: Eq>(param: T) { ... } 

but in this case, f32 cannot be passed to this function: for example, as we saw earlier, it only supports PartialEq .
If such restrictions are not significant, and the function is stable for any parameters, then the semantics will look like this:

 fn bar<T: PartialEq>(param: T) { ... } 

This function with less restrictions (you can play here )

From the fact that Eq is one more limit than PartialEq , and does not differ from it in any way in the “less” limiting direction, the following important conclusion follows:

All structures that implement Eq must also implement PartialEq

That is, the #[derive(Eq)] entry is not valid, if PartialEq is not defined on the structure, it PartialEq error!

The easiest way to avoid this:

 #[derive(Eq, PartialEq)] enum Foo { ... } 

Recall our function fn foo<T: Eq>() from the previous example. According to the semantics in it, T defined as Eq , but it turns out that it automatically accepts PartialEq structures as well.

Another property of Eq and PartialEq is that they cannot be automatically extracted for structures whose at least one element does not implement Eq and PartialEq respectively. For example:

 #[derive(Eq, PartialEq)] struct Foo { bar: f32 } 

will give an error, because, as we have already found out, f32 does not implement Eq .

Similarly, the following code will not work:

 struct Foo {} #[derive(PartialEq)] struct Bar { foo: Foo } 

because our structure Foo does not implement PartialEq (you can play here )

However, this behavior corresponds to all derivable traits . This we will see next

Conclusion: when writing code, it will be reasonable to implement Eq (where possible) or at least PartialEq in all public structures - it will be useful! And it will save a person who uses your code from sudden compilation errors when he decides to compare a couple of objects, or allows him to include your structure in his own, realizing comparison, without unnecessary tambourines!

Ord and PartialOrd


Again two similar traits, but this time they are responsible for “more-less”. However, they differ more than Eq and PartialEq :

PartialOrd PartialOrd adds comparison functions: > , < , > = , <= PartialOrd = , And implements loose ordering: Option <Ordering>

 use std::cmp::Ordering; let result = 1.0.partial_cmp(&2.0); assert_eq!(result, Some(Ordering::Less)); let result = 1.0.partial_cmp(&1.0); assert_eq!(result, Some(Ordering::Equal)); let result = 2.0.partial_cmp(&1.0); assert_eq!(result, Some(Ordering::Greater)); let result = std::f64::NAN.partial_cmp(&1.0); assert_eq!(result, None); 

Trait Ord implements "strict" ordering: Ordering

 use std::cmp::Ordering; assert_eq!(5.cmp(&10), Ordering::Less); assert_eq!(10.cmp(&5), Ordering::Greater); assert_eq!(5.cmp(&5), Ordering::Equal); 

If you look at the dependencies of these traits, then everything becomes clear:

To implement Ord , the structure must implement Eq .
To implement PartialOrd , the structure must implement PartialEq

Indeed, is it possible to say for sure that the value is “greater” or “greater than or equal to” relative to the second, if it may turn out that the structure is not equal to itself?

Note also that Ord requires a PartialOrd implementation, so all comparison operations for such structures will work guaranteed. In general, the next is a complete analogy with Eq and PartialEq .
You can play with all this farm here .

Conclusion : by analogy with Eq and PartialEq , we try to implement Ord or at least PartialOrd in all structures where it is possible and logical. Accordingly, structures with Eq will be able to support Ord , structures with PartialEq - PartialOrd .


Clone and Copy


Even in Russian, the words "cloning" and "copying" are synonyms with a difficult difference. But in the Rust language - these are different things. Now with them and we shall understand

Clone allows you to create T from &T using copy.
Copy allows you to copy rather than " move " the value of a variable during assignment.

In short, these traits are generally responsible for different things, they are connected only by the fact that:

To implement Copy , the structure must implement Clone .

Clone is essentially a common thing. It is implemented for almost everything that can be assumed. Yes, and copying an object by returning a generated copy of an object from the clone() function is not such a big problem, no matter how such an object is stored in memory - we create a new object, bring it to the same form, return it.

Copy is another song. In essence, the “copy” here is a one-by-one object transfer byte. And not all structures support this functionality. For example, if the pointer is “stupid”, two pointers will be created, showing the same place, which completely contradicts the “safe” ideology of Rust. Or, for example, objects that store some kind of metadata also cannot be copied byte-by-byte.

However, Copy makes it easier to deal with the transfer semantics.

 #[derive(Debug, Copy, Clone)] struct Foo; #[derive(Debug, Clone)] struct Bar; fn main(){ let foo = Foo; let bar = foo; println!("foo is {:?} and bar is {:?}", foo, bar); let foo = Bar; let bar = foo; println!("foo is {:?} and bar is {:?}", foo, bar); } 

First println! work, the second - no. And the difference is in Copy .
You can play with it here .

Conclusion : as advised in the official docks, we implement Copy in general, wherever possible. And it is impossible, in general, to implement it where Drop is implemented. Because, in general, if you need to call a destructor for a structure, it means that it is made in such a way that you cannot copy it with a simple memory replica. Clone does not seem to interfere anywhere.


Hash, Default and Debug


Treyt Hash is responsible for the possibility of taking hash from the structure. This is a prerequisite for compiling a HashMap or HashSet from such structures.

Trait Default is responsible for the initial (default) values ​​of the newly created structure. Structures that implement Default are often required in various data libraries. Also, the implementation of this trait is useful for structures that characterize the parameters of a system - there are always default parameters, which are too lazy to write every time :)

Treyte Debug is responsible for displaying the structure as a text formatted string. Nowhere is required, except in logging libraries, but it is useful when debugging your own programs.

The traits are different on their own, combined them because they derive same way from derive :

Hash , Default , and Debug can be implemented in those structures, all of whose members support Hash , Default and Debug respectively.

And this is not only necessary, but also sufficient. No unnecessary trouble, treit and anything else. Play with this farm here .

Conclusion : Hash can be implemented in all public structures - will not be superfluous. Default and Debug - to your taste, but desirable. This will help people using your code not to have problems with incorporating your structures into their own. In short, if it’s not a pity, we’re typing #[derive(...)] and pouring everything from the bounty there.


One and Zero


As of this writing (Rust 1.6 Stable), these traits are declared unstable. However, in all likelihood, in the future they will be able to define vector spaces for arbitrary data structures, when used together with the operations of addition ( Add ) and multiplication ( Mul ). To implement these traits, your structures must have the following properties:

For One : use in conjunction with Mul - x * T::one() == x
For Zero : use in conjunction with Add - x + T::zero() == x

These traits can be declared using a derive , if all members of the structures also support One or Zero .

Conclusion : if you do not exclude the possibility that your public structures will ever be used in vector spaces, such as participation in all sorts of algebras there, then it makes sense to implement these traits for the structures. But so far, any mention of these traits causes the compiler to only swear ...

The most final varning


If a field that does not implement this treit enters a structure that implements a Trait through derive , your code will fall apart. And if, because of this, you remove the implementation of your treit, then the code will fall apart from people using your structures. Therefore, the golden rule :

The more traits, the greater the responsibility.

Source: https://habr.com/ru/post/277461/


All Articles