- In the previous section,
we looked at a trait called PartialEq.
It finds a method which allows you to compare two objects
for equality.
There's also a trait called Eq, as we can see here.
And Eq inherits from PartialEq,
so it's a bit odd, the way that it works in Rust.
But it's the PartialEq trait
which actually defines the methods for equality,
and not equality.
And then inheriting from it, we have this trait called Eq.
Okay, so bear with me.
I've called it a marker trait.
A marker trait is one that doesn't have any methods.
So there aren't any new methods in the Eq trait.
It's almost like a compiler flag, to say that my type,
you could have a type here which implements the Eq trait,
and therefore obviously it would also have to implement
the PartialEq trait as well.
So, this is quite a confusing thing.
Let's discuss what the difference is
between these two traits.
The code is actually very simple.
It's understanding the application of the concept
is the interesting thing.
So, the Eq trait inherits the Eq method,
for equality tests, from PartialEq.
And also implements, or inherits, I should say,
the NE, not equals, it inherits that method,
for not equals tests, from there as well,
it doesn't add any new methods at all.
So what's the point of that?
You might be wondering, you could have a type,
imagine you've got a structure,
which just implements PartialEq.
Then you've already got enough
to be able to test for equality and inequality.
Why bother implementing the Eq trait instead?
But what the Eq trait does,
if your structure implements Eq.
I'll just draw that back in again.
If your structure is kind of here.
And it implements the Eq trait.
And therefore obviously also implements PartialEq.
It tells the compiler a little bit more about your type.
Okay, for a type which implements the Eq trait,
the compiler assumes
that you've implemented the equality tests
in such a way that you have equivalence relationships.
There are three extra things the compiler can assume
if your type equal implements Eq,
rather than just PartialEq.
So if your structure here implements the Eq trait,
the compiler assumes that you have reflexivity.
You can say X equals X.
It means, basically,
the compiler couldn't know this unless you tell it.
If your type implements the Eq trait,
then the way you've implemented the equality test,
is such that it's okay to compare the object against itself.
It also implies symmetric equivalence.
Okay, so in other words, you've written your equality method
so that if X equals Y, then it assumes that Y also equals X.
So it's symmetric.
And also transitive.
And all of these promises, the compiler assumes,
if your structure implements the equality trait, Eq,
it assumes that you've implemented your logic,
such that it's transitive, and symmetric, and reflexive.
Okay, so it can make optimizations in your code.
Transitive equality is if X is equal to Y,
and Y is equal to Z, then it implies X is equal to Z.
Okay, so it's transitive.
All of these three assumptions the compiler will make
if your structure implements Eq.
On the other hand, if your structure didn't implement Eq,
but it just implemented PartialEq.
Okay?
Then just because X equals Y,
the compiler couldn't assume that you've written the test
so that this would also prove true.
It couldn't assume that.
And it couldn't assume
that if X equals Y then Y also equals X.
And it couldn't assume transitive,
it couldn't assume that because X equals Y, and Y equals Z,
it doesn't necessarily imply that X equals Z,
because you haven't maybe implemented that way,
you haven't told the compiler
that those assumptions are true.
To tell it that these assumptions hold true,
then you implement the Eq trait.
Here.
So it is quite common for you to implement both.
Remember, if you implement a trait,
you must also implement all of its super traits.
So if you implement Eq,
then you must also implement PartialEq,
and it's the PartialEq
which does the actual work of comparison.
I think most Rust developers
scratch their heads at this for a while, thinking,
"That's a really kind of interesting design decision."
But that's what it is.
So, the easiest way to support Eq is just by hash derive.
Because it's a market interface,
because there's no actual method to implement.
It's just like a stamp, to say, yes, I support this trait.
Then why bother actually implementing it manually?
You just basically mark your structure as implementing,
deriving the Eq support.
You've also, of course, gotta implement PartialEq as well,
because that's a super trait.
So, what I've done here, is I've got a class,
or structure, I should say, called EmpCode.
It represents like a primary key
for an employee and a company, and it's a composite key.
The employee's code contains the country that they work in,
like the UK, and their employee number within that country.
Okay, so it is like a composite key.
It could be that in the UK,
you could have employees one through to 100.
In Singapore, you could have employees one through 100,
you need to know both the country, and the employee number,
to identify, uniquely, an employee.
That's the kind of example I've got.
So, I've derived implementation of Eq.
And I've also derived implementation of PartialEq.
Remember how that works, from the previous section?
If you derive automatic implementation of PartialEq,
the compiler will generate an equality test,
that'll do an equality test on field one,
and an equality test on field two.
So two EmpCodes
that had the same country and employee number as strings
would compare equal.
That's the PartialEq implementation.
And the Eq implementation is just a marker
to say it's transitive, reflexive, and symmetric.
So it's really easy to actually implement the Eq trait,
once you understand what it's for.
Now then, if you implement the Eq trait,
you might also want to implement another trait called hash.
This is quite common in languages like C#, and Java,
where you have like an equals method,
and when you implement equals,
you typically also implement
some kind of get hash code method as well.
So if your structure implements hash
then your structure can be inserted into a hash set.
A hash set has unique collections.
A collection of unique elements, I should say.
And when you insert an item into a hash set,
the compiler has to be able to call
some kind of hash algorithm on your object,
to figure out which bucket to put it in,
so it can retrieve it again quickly.
It uses a hashing mechanism to locate items quickly.
So if your structure type implements the hash trait,
then your structure type, like EmpCode,
could be inserted into a hash set.
If it didn't implement hash,
then it couldn't be implemented
and inserted into a hash set.
And the other thing you can do, if you implement hash,
is your type can be used as a key in a hash map.
Okay, you could have a hash map,
where the key type is like employee code.
So you look up the code,
and you get back a value, again, hash key.
When you insert a key value pair into a hash map,
the key is hashed.
It performs some kind of integer algorithm on it
to figure out which bucket to put it in, in memory,
for fast retrieval, for indexing, basically.
So if you want your type to participate in hashing,
then you can implement the hash code,
the hash trait as well.
And that's quite common.
It is quite common to implement
both Eq for equality checking, and hash for hashing.
Right, so again,
the most typical way to support the hash trait
is via derive.
So you get the compiler
to generate an implementation for you.
And this is possible
if each field in your structure implements hash.
Like string.
String implements hash, there's a hash mechanism in strings.
So, the resultant hash of your structure
will be a combination of the hash code for each field.
You don't actually need to worry how it works, internally.
You can override the mechanism if you want to,
but generally this will be sufficient.
So it looks like this.
Here is my revamped EmpCode structure.
And the key point here, ah, the key point, no pun intended,
is that my EmpCode supports hashing.
So I could insert, I could use EmpCode,
as the key type, in a hash map, bear that in mind.
I can also check for equality using PartialEq.
And I can check for equivalence, in other words,
symmetrical, transitive, and reflexive equality as well,
because of the Eq trait.
Right.
So, let's apply all this in an actual example,
going back into our project.
Lesson13_generics.
We'll start off with main.
And then the most interesting part of the demo
will be in demo_eq_hash.
It illustrates the Eq trait,
and the hash trait implementation.
And then of course we'll run the project,
let's have a look.
This is quite exciting I think.
Right, in Main,
well, obviously in a work-related kinda context.
So, that was the previous demo,
and here is my implementation of demo_eq and hash,
let's uncomment that.
And it's here.
So, I've got an EmpCode,
which is like a key to look up an employee.
And I've benefited from off-the-shelf implementation,
isn't this a wonderful mechanism, now?
You can get the compiler
to automatically derive or generate
implementation of the Eq trait, which is a marker trait.
The hash trait, which will basically calculate the hash code
by combining the hash code of the country,
and the hash code of the employee number,
both of them strings, and they both implement hash.
And also, off the shelf,
generated implementation of PartialEq.
It'll generate an Eq, sorry, yeah, an EQ method,
and an NE method, based on equality checks
for the countries of two objects.
And the empnum values for two EmpCode objects.
And if those two values are equal,
if my EmpCode number one, country and emp number,
is the same as EmpCode number two, country and emp number,
then those two objects would be deemed equal,
and that's the default implementation provided there.
So that's like a composite key type in a database,
that's the kind of image to have in mind.
In terms of implementation, all I've done
is to just implement an associated new function,
just to create an instance,
this is quite common now, we've seen this a lot, haven't we?
I pass in a atring slice for the country code.
And a string slice for the employee number, like 007.
And it returns an EmpCode.
It returns an EmpCode whose country field
is a stringification of the incoming country as a slice.
So that means I can pass in a string literal, remember.
And the employee number as well.
So that EmpCode is gonna be constructed.
So EmpCode taken together is kind of like my composite key.
I'm gonna use that as a key type in the dictionary.
Let me show you that, actually, in my code down here.
In my duet code, I've created a collection,
a hash map, where the key is EmpCode.
Okay, now I can only use EmpCode as a key type,
because EmpCode implements hash.
If EmpCode didn't implement hash,
then I couldn't use EmpCode as the key type.
You can only use hashable types as the key.
So you'd look up an EmpCode,
and you'd get back the employee who has that code.
I've got an emp structure as well.
So let's have look at the emp structure,
this is the value type in my hash map.
So an employee has a name and a salary.
Oh, only a 32-bit salary, okay.
Not as large as it might be then.
Oh, and also, I've got debug support built in.
Again, that's quite handy,
I can just output an employee using the debug formatter
and it'll automatically just output the name in the salary.
Do I have any implementation for my employee?
Okay, just the constructor, basically,
just to basically create a new employee instance,
with a given name and salary.
So not a lot going on there, it's a fairly simple data type.
Okay, so, staff is my hash map.
Here's my mutable hash map,
which contains an EmpCode as the key,
and an employee as the value, empty initially.
I can insert.
This will create an employee code UK 001,
so country code, employee number.
In the UK, this is employee number 001.
And it's Matt and he earns 1000, 1000 units of salary.
And I've inserted, into the UK, employee number two is Mark.
And then, I've also inserted, in the US,
employee 001 in the US is Mary.
Notice that the employee number is the same as here.
But this was for a UK-based employee,
and this is for a US-based employee.
So they're different.
And then I do a lookup, I say in my staff, lookup,
I create an EmpCode.
I create an EmpCode, this is the key that I'm gonna look up,
look up in my hash map.
Look up.
That's the employee code.
I'm gonna look up employee 002.
It takes a reference,
so you have to give it a reference here,
the ampersand there, it borrows that EmpCode value.
And I'm hoping it'll give me back
which employee has EmpCode 002.
I'm hoping it's going to be giving me back this value here.
So I'm hoping this will return back a reference to Mark.
Okay, I'm gonna output Mark using the debugger.
That's what I should see.
Okay, so let's run it then.
Okay, so quite a lot going on.
Of course, the only output that we see,
is that we've successfully looked up UK-based employee 002,
using this kind of composite key approach,
which implements hashing, and equality checks.
And it'll give me back employee Mark, with a salary of 2K.
Fantastic.