Traits out of the box
Often see:
#[derive(RustcEncodable, RustcDecodable, Clone, Eq, Default)]
struct Foo {
}and don’t understand what it is and where?
The compiler itself can provide you with simple "built-in" implementations for some traits using
#[derive]. Of course, these same traits can also be implemented manually if more complex behavior is required. So, here is a sample list of traits that can be "extracted" (derive translates that way):
- [↓] Comparison traits: Eq , PartialEq , Ord , PartialOrd
- [↓] Clone and Copy are traits responsible for cloning and copying.
- [↓] Hash - to calculate the hash
&T - [↓] Default - allows you to set the "default value"
- [↓] Debug - defines the format for outputting the structure value when using the
{:?}formatter
In addition, unstable:
All of the above traits are in the standard library
std. In addition, you can find libraries made with your own hands, where there are also traits that can be extracted using #[derive]. An example is the commonly used RustcEncodable / RustcDecodable . You can implement support derivefor your traits using 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
Compare Traits
They allow you to arrange for your structures a model of equivalence relations . Subsequently, the structures can be compared, organized, structured and organized. Let's consider them in more detail.
Eq and PartialEq
Two similar traits are responsible for what can be said
foo == baror not. But why such a simple thing two different traits? And the solution is this:PartialEq does not guarantee equality with oneself: 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 that we are the creator of a library that has some sort of comparison function:
fn foobar(param: T) {
...
} How to choose a restriction for
T? If complete equivalence is fundamental to us, and without it the function will fall apart, we will write it like this:fn foo(param: T) {
...
} but in this case, for example, it will not be possible to pass into this function
f32: as we saw earlier, it only supports it PartialEq. If such restrictions are not significant, and the function is stable for any parameters, then the semantics will look like this:
fn bar(param: T) {
...
} This function with less restrictions (you can play here )
. Since
Eqthere is one more restriction than PartialEq, and does not differ from it in any way that is “smaller” in terms of restrictions, the following important conclusion follows:All structures implementingEqshould also implementPartialEq
That is, a record
#[derive(Eq)]is not competent, if not defined on the structure PartialEq- will give an error! The easiest way to avoid this:
#[derive(Eq, PartialEq)]
enum Foo {
...
}Recall our function from the previous example. According to semantics, it is defined in it as , but it turns out that it automatically accepts structures. Another property and - they cannot be automatically extracted for structures, at least one element of which does not implement , respectively. For example:
fn foo() TEqPartialEqEqPartialEqEqPartialEq#[derive(Eq, PartialEq)]
struct Foo {
bar: f32
}will give an error, because, as we have already found out, it
f32does not implement Eq. Similarly, the following code will not work:
struct Foo {}
#[derive(PartialEq)]
struct Bar {
foo: Foo
}because our structure
Foodoes not implement PartialEq(you can play here ) However, this behavior corresponds to all
derivabletraits . We will see this further. Conclusion: when writing code it will be wise to implement
Eq(where possible), or at least PartialEqin all public structures - it will come in handy! And it will save the person using 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, implementing the comparison, without any tambourines!Ord and PartialOrd
Again, two similar traits, only this time responsible for more-less. However, they differ more than
Eqand PartialEq:TraitPartialOrdadds to the comparison function: > , < , > = , <= , ! = , = And sells non-strict ordering:Option
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);The traitOrdimplements "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 implementOrd, the structure must implementEq.
To implementPartialOrd, the structure must implementPartialEq
Indeed, is it possible to say for sure whether the “greater” or “greater or equal” value is relative to the second, if it may turn out that the structure is not equal to itself?
We also note that it
Ordrequires implementation PartialOrd, therefore, all comparison operations for such structures will work guaranteed. In general, further - a complete analogy with Eqand PartialEq. You can play with all this economy here .
Conclusion : by analogy with
Eqand PartialEq- we try to implement, Ordor at least PartialOrdin all structures, where it is possible and logical. Accordingly, structures with Eqcan support and Ord, structures with PartialEq- PartialOrd.Clone and Copy
Even in Russian, the words “cloning” and “copying” are synonyms with an indistinguishable difference. But in the Rust language, these are two different things. Now we will deal with them
Cloneallows you to createTfrom&Tusing copy.Copyallows you to copy, not " move " the value of a variable when assigning.
In short, these traits are generally responsible for different things, they are connected only by the fact that:
To implementCopy, the structure must implementClone.
Cloneessentially a common thing. It is implemented for almost everything that can be assumed. And copying an object by returning the clone()generated copy of the object from the function is not such a big problem as such an object would not be stored in memory - create a new object, bring it to the same form, return it. Copy- another song. In essence, the “copy” here is a one-by-one transfer of an object by byte. And not all structures support such functionality. For example, with a “dumb” copy of a pointer, two pointers are created that point to the same place, which completely contradicts the “safe” Rust ideology. Or, for example, objects that store some kind of metadata also cannot be copied byte-by-bye. However,
Copy#[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);
}The first
println!will work, the second will not. And the difference is in Copy. You can play with it here .
Conclusion : as advised in official docks, we implement it
Copygenerally wherever possible. But you can’t implement it, in the general case, where Drop is implemented . Because, in the general case, if a destructor needs to be called for a structure, then it is made in such a way that you cannot copy it with a simple memory cast. Clonedoesn't seem to hurt anywhere.Hash, Default, and Debug
The trait
Hashis responsible for the possibility of taking a hash from the structure. This is a necessary condition in order to be able to compose from such structures HashMap or HashSet . The trait
Defaultis responsible for the initial (default) values of the newly created structure. Implementing structures are Defaultoften required in various data libraries. The implementation of this trait is also useful for structures that characterize the parameters of a system - there are always default parameters that are too lazy to write each time :) The trait
Debugis responsible for displaying the structure in the form of a text formatted string. It is not required anywhere, except in the logging libraries, but it can be useful when debugging your own programs.Traits are different in themselves, combined them because
derivethey work the same way:Hash,,DefaultandDebugcan be implemented in those structures all of whose members supportHash,DefaultandDebugaccordingly.
And this is not only necessary, but also sufficient. No extra hassle, traits and anything else. You can play with this farm here .
Conclusion :
Hashit can be implemented in all public structures - it will not be superfluous. Defaultand Debug- to your taste, but preferably. This will help people using your code to not have problems when incorporating your structures into theirs. In short, if it’s not a pity, we recruit #[derive(...)]and pour everything from 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 with
deriveif all members of the structures also support Oneor Zero. Conclusion : if you do not exclude the possibility that your public structures will someday be used in vector spaces, such as participating in all sorts of algebras there, then it makes sense to implement these traits for structures. But so far, any mention of these traits causes the compiler to only swear ...
The most concluding warning
If a field that does not implement this trait enters your structure that implements some kind of
Traitone derive, then your code will fall apart. And if because of this you remove the implementation of your trait, then the code will fall apart among people using your structures. Therefore, the golden rule :The more traits, the more responsibility.