- Imagine you want to compare two objects for equality.
Like I've done here,
I've defined a point structure,
an XY coordinate,
I've created some point objects.
What I'd like to do
is to compare point objects for equality
and for inequality.
I'd like to do that
but unfortunately this code won't compile
because the point structure
doesn't support equality and inequality.
Rust doesn't know what it would mean
to compare points for equality.
How could it possibly know?
You have to give it some help.
The way to support inequality and equality checks in Rust
is to implement a trait called PartialEq.
There's a similar trait we'll discuss later on called Eq
and I'll explain how the Eq trait differs from PartialEq.
Okay, but to get the ball rolling
we will just discuss the the most primitive of these two
which is the PartialEq trait.
It has two methods.
It takes a parameter,
a type parameter,
representing the other kind of object
you wanna compare yourself against.
And by default
it assumes that the right hand side type
is the same as you, okay?
So in other words
if the left hand object is a point,
it assumes that the right hand side object
is also of that type point.
You're comparing one point against another.
We'll show an example of this later.
There's one method you have to implement
it's the Eq method.
It takes you plus a reference to the other object
which is presumed
to be of the same type as you
and it you have to implement this method
to return a Boolean.
In other words, you have to tell Rust
what does it mean for two objects to be equal.
There's also a not equal method NE,
but that has a default implementation.
So in practice
you typically just implement the first method Eq.
Rust will implement not equal
automatically as an inverse of the equality check.
So if it knows how to do equality
it can infer how to do inequality.
These functions are called automatically
when you use the operators.
So when you use the equals equals operator
it'll automatically call the Eq function
that you've implemented.
And when you use the not equals operator
it'll call the not equals function
which has been presumably provided by Rust.
Now the easiest way for you to support PartialEq
for a structure is via hash derive
and I've done that here.
As long as each field in here
supports the PartialEq type,
i32 does support Partial equality
and we know that
because we can say I equals J.
Integers know how to compare for equality.
So as long as each field in your structure
supports PartialEq type
then you can just use hash derive, okay?
On PartialEq or what the Rust compiler will do.
When you compare two points for equality
like I done here
I've got some points being created.
When I compare P1 equals P2
it'll invoke the default version of the Eq method
as generated by the compiler
and it'll compare the X of P1
for equality with the X of P2
and it'll compare the Y of P1 and the Y of P2.
So it kind of recursively calls the equality operations
for each field
to determine if the whole point
is equal to the other point.
If every field in here
is equal to every field in here,
then the equal, okay?
And that's what happens here.
So this syntax is implicitly calling the equality function.
You can call it explicitly if you want to as well.
Not much point in doing this to be frank,
but you can you can pass in point 1.Eq
I'm explicitly calling the equality function
it'll pass P1 itself
and it'll pass a reference to P2 as the other parameter.
Here's the not equal comparison
using the operator
and here's the not equal comparison using the function.
Obviously the point of these operators
is that you use the operator syntax
rather than using the function syntax
unless you really want to.
Okay, so that's good.
Let's take an example of this.
It's in the lesson 13 Generics project.
We'll have a look at the main.rs at the start.
I've got a an application file
called Demo Partial equality
using the derived mechanism, okay?
So we'll run that first.
Let's have a look.
So in my main code
I've got three as it turns out
different demos of how to implement Partial equality.
First of all, using the simplest approach
using the derived mechanism, okay?
So that's the first demo we'll have a look at
and it's here.
So I've got a point structure
which doesn't implement Partial equality.
I've got another point structure that does, okay,
so here's a function,
demo no Partial equality.
In this example here,
I've created instances of my point structure
that doesn't support Partial equality okay?
Point V1, doesn't support equality checks.
So if I tried these statements here, they would fail
because my point structure doesn't implement PartialEq.
All right?
So it would be disappointing.
More fruitful would be the second demo,
demo partial equality.
And here I'm using P2, point V2 I should say.
And that structure does support partially quality.
So these will now work,
I'll just prove the point
that they do indeed work.
cargo run.
So I'm checking my points,
I'm seeing does P1 equal P2?
And it does in both syntaxes.
Is P1 different from P3?
Is it not equal?
Is P1 not equal P3?
And the answer is true.
P1 and P3 are different.
Okay then, so that is the simplest possible way
to implement Partial equality
using the derived attribute.
What we're gonna do in the next demo
is see how to implement Partial equality
specifically where the algorithm is a bit more complicated
than the compiler would be able to guess.
So let's see how to do that.
So you can provide a custom implementation of PartialEq
useful where the equality check
isn't just a matter of saying A equals A,
B equals B
but something more profound.
So here's an example.
I've got a class or structure I should say called angle.
It represents an angle in a circle, you know?
and what I want to do is to have the concept of wraparound.
So every time it goes around 360
it's basically the same angle.
One degree is the same as 361 degrees.
Two degrees is the same as 362 degrees
in the sense that it land up pointing in the same place.
Okay, so that's a bit of a contrived example,
but here we go,
I'm implementing PartialEq
explicitly for the angle structure,
the equality now, self will be an angle,
other will be the same type
and other will also be another angle.
Okay so I've got two angles
I take my degrees and modulus 360,
in other words I basically divide by 360
and just keep the remainder.
So if it's 360 or 720 or whatever 1,080,
is basically the same angle.
So take the modulus of the first angle
and take the modulus of the second angle.
And if those numbers are left over as the same
they are the same amount around the circle.
So that's a non-trivial comparison.
There's no way the compiler
could possibly have guessed that
because it doesn't understand angles like we do.
There we are.
So I'm gonna show you that example again.
It's gonna be in same project,
but this time PartialEq
using an implemented approach explicitly.
Okay so that's this one,
Demo PartialEq implemented explicitly
and it's here.
Okay so here is my angle structure
and here's my explicit implementation of PartialEq.
And again, I'll just remind you
that when you implement methods for a trait
they are implicitly public.
You don't need to say pub,
in fact, you can't
they are public anyway.
I've got some angles here.
90 is the same angle as 450, really.
450 is once around the circle
and then 90 degrees extra
so it'll end up pointing to the same place.
So in the kind of wraparound kind of way,
A1 and A2 are equal
and they will be tested as equal here.
And angle one and angle three are different
and they will prove to be different here.
So let's run the example again
and in my implemented partial equality
angle one and angle two are true.
Angle one and angle three are different, okay?
So one reason
for implementing the partial Partial equality
structured trait type yourself
is where the algorithm is something
that the compiler couldn't guess, okay?
And that's one reason
for implementing Partial equality explicitly.
There is another reason
and that's where the type of object
you're comparing against is a different type.
And that's what we're going to look at
in our third and final example.
So it's possible for a structure
to compare itself equal
against a different structure type.
Here's an example,
I've got a Time Seconds
it'll give you the time in seconds.
It derives support for partial equality.
In other words, you can say
something like time in seconds one
equals equals a time in seconds two.
It understands comparing against time seconds.
I've also got a time in minutes
which also has derived support for Partial equality.
In other words, if you had a time in minutes one
then it would understand
how to do a time in minutes two, right.
But what if I wanted to do time second one
equals time minute one?
Oh double equals, excuse moi.
What if I wanted to compare seconds and minutes?
Well that's what this implementation does.
It says for the time seconds structure, okay
so that would be the self parameter.
I can compare against time minutes.
So I've implemented time minutes for time seconds.
So it'll allow me to compare time seconds
against time minutes like so and vice versa.
Imagine I had a time in minutes
and I wanted to compare against the time in seconds, okay?
Because expressed in minutes and seconds
they might be the same value
but they're different types.
So these derived structures
they only work if the types are the same.
What I've done here,
is I've said for time minutes,
I've implemented a comparison
against apart with time seconds
being the other type of parameter coming in.
So the other type of parameter
doesn't need to be the same as me,
it can be a different type like so.
Okay, so this doesn't happen very often obviously
but it could happen
and you can implement,
basically specifying a type here
which is different from your type
and then you implement the logic.
So I'm gonna show you the logic for that.
Again, same project, we'll start off with main.
The code we're going to run
is in demo PartialEq implemented different types
and then we'll run it.
Okay so demo PartialEq implemented for different types
and here we are.
So I have my time in seconds,
which will know how to compare itself
against another time in seconds.
I can say time in seconds one
equals time in seconds two already.
Likewise, I can compare two time in minutes for equality.
It'll understand time minutes one equals time minutes two.
And then I've implemented
a combination of seconds and minutes
and minutes and seconds.
I've done it both way rounds.
It's kind of associative,
I said for time seconds, okay?
So self will be time seconds.
I can compare a time in seconds
against a time in minutes.
So a time in seconds
equal a time in minutes,
that would be true.
A minute is 60 seconds.
So if you multiply the other object minutes by seconds, 60
then it'll check compare the actual seconds like so.
And conversely,
if I have a time in minutes
so self is a time in minutes
I can compare it against some other type in seconds.
And you'll notice here
that if you know how to compare seconds and minutes
then minutes and seconds is just the other way round.
So I've just leveraged the operation I've defined above.
This function here
allows me to compare seconds against minutes.
Okay, so other here is seconds.
So that is using seconds against minutes.
This function here is calling that function there.
I've avoided duplicating the code.
I've just used the same function
but swapped the parameters around.
Okay, I was quite pleased with this
when I thought about it.
So I've got some times and seconds
and I've got some times and minutes.
I can use the derived implementation
to compare seconds
and inequality for seconds.
So whenever I'm dealing with seconds everywhere
then it'll be using the inbuilt seconds comparison.
Likewise, it'll use the inbuilt minutes comparison
if all I'm doing is comparing minutes with each other
like I'm doing here
and comparing minutes against minutes.
So that's simple.
And in this case I'm comparing seconds and minutes
in doubt in various different syntaxes.
So seconds
is the structure type I'm working with
and minutes is the other type
so that will be provided by this one here.
For my seconds where time seconds is self,
the other type of object
it'll be seconds equals equals minutes, okay?
So those are supported here,
seconds against minutes in any different syntax we like.
And then the other way round
minutes against seconds
the object on the left
always generates the self pointer, okay?
So it's the minutes structure that we are looking at.
Can I compare minutes against some other type seconds?
Yes I can
because for minutes
I've implemented Partial equality with seconds, okay?
So self will be minutes before the equals operator.
The other parameter after the equals will be seconds.
So I can say minutes equals equals seconds.
So all we need to do is to run this
and I really hope it works.
I think it is going to work, isn't it?
Yes.
So we can compare seconds through equality
using the derived implementation.
Okay, so that's the derived implementation
seconds against seconds here
and then we can compare minutes against minutes, okay?
And that's again using the derived implementation.
And if that's as far as I needed to go
then we could stop there.
But I also want to support seconds and minutes.
So for my time seconds,
I support equality against minutes.
Okay, so the other type coming in is minutes.
So that implementation here covers this.
And then finally,
if I have minutes to compare against seconds,
then for my minutes structure
I can then compare against seconds.
It's this implementation
that supports these operations here.
Hooray.