🦀 Rust: Eq vs PartialEq
tags
type
Post
summary
status
Published
slug
rust-eq-vs-partialeq
date
Apr 1, 2023
The Rust Project or Foundation has not reviewed/approved/endorsed this content. :)
In Rust, if you want to overload operators, you need to implement the corresponding traits.
For example,
<
, <=
, >
, and >=
require the implementation of the PartialOrd
trait:use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }
Similarly, the
+
operator requires the implementation of the std::ops::Add
trait, and the main characters of this article, Eq
and PartialEq
, correspond to the ==
and !=
operators. So, what is the difference between these two traits?We may not be very clear on this point, and even if we consult the documentation, we may not find a particularly clear explanation. If you have looked at the examples in the standard library, you may have seen this one:
enum BookFormat { Paperback, Hardback, Ebook } struct Book { isbn: i32, format: BookFormat, } impl PartialEq for Book { fn eq(&self, other: &Self) -> bool { self.isbn == other.isbn } } impl Eq for Book {}
Here, only
PartialEq
is implemented, and Eq
is not implemented, but the default implementation impl Eq for Book {}
is used directly. Strange, right? Don't worry, there's more:impl PartialEq<IpAddr> for Ipv4Addr { #[inline] fn eq(&self, other: &IpAddr) -> bool { match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } } } impl Eq for Ipv4Addr {}
This code comes from the Rust standard library, and as you can see, it is still used in this way, with countless similar situations. So does this mean that if we want to add equality comparison for our type, we only need to implement
PartialEq
?Actually, the key point is the word "partial". If our type only has equality in certain situations, then you can only implement
PartialEq
, otherwise you can implement PartialEq
and then use the default implementation of Eq
.Okay, the problem is gradually becoming clear, and now we just need to figure out what partial equality means.
🦀 Partial Equality
First, we need to find a type that implements
PartialEq
but not Eq
(you may wonder if there is a reverse situation? Of course not, partial equality must be a subset of total equality!)In the
HashMap
section, it was mentioned that the key of a HashMap
must implement the Eq
trait, which means it must be completely equal. Floating-point numbers cannot be used as the key of a HashMap
because they do not implement Eq
.So, let's consider why floating-point numbers can be compared even though they do not implement
Eq
:fn main() { let f1 = 3.14; let f2 = 3.14; if f1 == f2 { println!("hello, world!"); } }
We can see the output of this code, which means that floating-point numbers do implement
PartialEq
. Let's verify this with a simple code snippet:fn main() { let f1 = 3.14; is_eq(f1); is_partial_eq(f1) } fn is_eq<T: Eq>(f: T) {} fn is_partial_eq<T: PartialEq>(f: T) {}
The above code uses trait constraints to verify our conclusion:
3 | is_eq(f1); | ----- ^^ the trait `Eq` is not implemented for `{float}`
Okay, since we have successfully found a type that implements
PartialEq
but not Eq
, let's use it to see what partial equality means.Actually, the answer is very simple. Floating-point numbers have a special value
NaN
that cannot be compared for equality:fn main() { let f1 = f32::NAN; let f2 = f32::NAN; if f1 == f2 { println!("NaN can be compared, this is not mathematical!") } else { // This will be the output println!("As expected, although both are NaN, they are not equal") } }
Since floating-point numbers have a value that cannot be compared for equality, it can only implement
PartialEq
but not Eq
. Similarly, if our type has this special requirement, we should do the same.But why can not the
NaN
be compared? It’s so complicated, but there’re some useful links: