We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 19 of 26
PartialEq and Eq: why floats can be compared but not Eq
Deduplicate a list of keys, keeping first-seen order, and flag any key whose entries disagree.
pub fn dedup_keys(keys: Vec<String>, weights: Vec<f64>) -> Vec<String>
keys[i] and weights[i] form one Weighted record. Return each distinct
key once, in the order it first appeared. If that key appears more than
once with different weights, append a !.
keys = ["a", "b", "a"]
weights = [1.0, 2.0, 1.0] -> ["a", "b"]
weights = [1.0, 2.0, 3.0] -> ["a!", "b"]
Two traits, one method between them
pub trait PartialEq<Rhs = Self> {
fn eq(&self, other: &Rhs) -> bool;
fn ne(&self, other: &Rhs) -> bool { !self.eq(other) }
}
pub trait Eq: PartialEq<Self> {} // no methods at all
Eq is empty. It adds no behaviour whatsoever. It is a promise: that
your equality is a genuine equivalence relation —
-
reflexive:
a == afor everya -
symmetric:
a == bimpliesb == a -
transitive:
a == bandb == cimpliesa == c
PartialEq promises only the last two. This is the first place in the course
where Rust encodes a mathematical law in the type system, with a trait
that contains no code at all, and it is worth pausing on how odd and how
useful that is.
Why f64 is PartialEq and not Eq
f64::NAN != f64::NAN. That is IEEE-754, not a Rust quirk. Reflexivity
fails, so floats are only partially equal, so f64 implements PartialEq
and deliberately does not implement Eq.
From that one fact everything else follows. HashMap and HashSet require
their key to be Eq + Hash, so you cannot use an f64 as a hash key —
not because Rust is being difficult, but because a key that is not equal to
itself cannot be looked up. BTreeMap requires Ord, whose supertrait is
Eq, so floats are out there too.
The starter makes the mistake for you. It derives PartialEq, Eq, Hash on a
struct containing an f64, and you get two errors at once:
error[E0277]: the trait bound `f64: Eq` is not satisfied
error[E0277]: the trait bound `f64: Hash` is not satisfied
A single line of derive, two distinct impossibilities, both named precisely.
Note also: if you try #[derive(Eq)] without PartialEq, you get
“can’t compare A with A“. Eq is a supertrait of PartialEq, not a
replacement for it — you always need both, and Eq is the extra promise on
top.
Eq is a promise the compiler cannot check
Nothing stops you writing impl Eq for Weighted {} by hand on a
float-containing type. It compiles. It is also a latent bug: a HashMap
keyed on it will behave unpredictably the first time a NaN arrives. The
compiler enforces that you claimed the property, not that you have it.
Derived PartialEq on an enum
Worth knowing: derived PartialEq compares the discriminant first. Two
different variants are never equal no matter what their payloads say —
Shape::Circle(1.0) != Shape::Square(1.0) — and identical variants then
compare field by field.
Lints in this family
partialeq_ne_impl fires if you override ne. The default
!self.eq(other) is almost always what you want, and an override is nearly
always a mistake.
derived_hash_with_manual_eq is a deny-by-default correctness lint and
the subject of its own item later in this track.
derive_partial_eq_without_eq (allow-by-default) nudges you to add Eq
where you could have — with the float exception you now understand.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.