A question about trait `Eq` and `PartialEq`


#1

I wrote some code

#[derive(Eq)]
struct Node<T> {
    data: T,
}

the complier complains:

error[E0277]: the trait bound Node<T>: std::cmp::PartialEq is not satisfied
–> src/main.rs:3:10
|
3 | #[derive(Eq)]
| ^^ can’t compare Node<T> with Node<T>
|
= help: the trait std::cmp::PartialEq is not implemented for Node<T>
= note: required by std::cmp::Eq

so I add some code:

impl<T: PartialEq> PartialEq for Node<T> {
    fn eq(&self, other: &Node<T>) -> bool {
        self.data == other.data
    }
}

then the compiler let it pass.

my question is: how can Node<T> inherit Eq when T did not implement PartialEq.


#2

I’m a little confused about your question… are you asking how you can get Node<T> to implement Eq when the underlying T doesn’t implement PartialEq?

This isn’t really possible because in order to do a useful comparison for some Node<T> you’ll eventually need to compare the inner data element, which isn’t possible when T doesn’t implement PartialEq.

It might help a bit more with an example. Given a type which looks something like the following

#[derive(Eq)]
struct Node<A, B> {
  a: A,
  b: B,
}

The #[derive(Eq)] part will expand (roughly) to the following:

struct Node<A, B> {
  a: A,
  b: B,
}

impl<A, B> Eq for Node<A, B>
where A: Eq,
      B: Eq
{
  fn eq(self, other: Self) -> bool {
    ...
  }
}

#3

(for my understanding of your question…)

Quoting from the ref: https://doc.rust-lang.org/std/cmp/trait.Eq.html

This property cannot be checked by the compiler, and therefore Eq implies PartialEq, and has no extra methods.

So, basically the following code compiles:

struct Node<T> {
    data: T,
}

// This is all `#[derive(Eq)]` does.
impl<T> Eq for Node<T> {}

// This is you telling what function to call on `==` binary operator
impl<T> PartialEq for Node<T> {
    fn eq(&self, other: &Node<T>) -> bool {
        false
    }
}

but is considered incorrect because for some (and all) Node a, you’ll have:

assert_eq!(a == a, false);

Therefore, you have a code that compiles, but is incorrect. Eq operator overloading is one of those cases that usually needs some (or at least just one) unit testing, if the implementation is not obvious.


#4

Couple of side notes:

  1. You can have types that implement both Eq and Default, but Rust has no batteries for testing if the default value provided for the type equals to itself. I find it a bit surprising, but maybe it’s just me.

  2. I also think that the case of Eq and PartialEq is a bit under-documented when it gets to enum types. Here’s my initial comment: https://github.com/rust-lang/rust/pull/42920#issuecomment-311236838


#5

The Eq trait has PartialEq as a dependency, so it’s not possible for a data structure to implement Eq without first implementing PartialEq.

The code should compile with the following amendment:

#[derive(Eq, PartialEq)]
struct Node<T: PartialEq<T>> {
    data: T,
}

It is worth noting however that Eq represents an equivalence relation which places extra restrictions on the implementation of PartialEq which the compiler can’t reason about. So if you really need Eq I’d recommend this amendment instead:

#[derive(Eq, PartialEq)]
struct Node<T: Eq + PartialEq<T>> {
    data: T,
}

#6

Thanks for your reply.

your example is good but did not solve my problem.

consider a scenario: if a type T does not impl PartialEq, so Node<T> does not impl PartialEq neigher, so this Node<T> can not inherit Eq, the compiler should refuse this code. but the compiler accepted it.


#7

Uh, I think now I get what your question is: Why compiler allows having self.data == other.data when you have not explicitly claimed that <T: PartialEq>?

So, I think this goes to Rust’s type inference: the compiler figured it out that T needs PartialEq, and everything’s fine unless you try to do break this (implicit) expectation.

To trigger an error and see the inference in action, try instantiating Node with a T that doesn’t impl PartialEq.

Does this answer your question?

(PS. I could be wrong about how the inference is in work here. So, please let me know if I got it wrong and that’s not what’s happening.)


#8

Ah, actually it’s simple: you previously only implemented PartialEq for Nodes whose inner data type T implements PartialEq. As a result, if you tried to instantiate a Node with a data type which does not implement PartialEq, it would compile, but you would get a Node type which does not implement PartialEq.

If your goal is to state that a contents of a Node must implement PartialEq, then you need to specify this requirement in the struct declaration (as shown earlier in this thread), not in the impl block. The logic behind this is that all impl blocks should be understood as optional in Rust.


#9

This is very interesting! Don’t remember seeing anything along this line in the book or other resources. Do you have a reference for this?


#10

I discovered the possibility to optionally implement some functions for a generic type depending on its bounds while I was looking at the source code of the “atomic” crate.

I’m not sure if this is directly documented anywhere at the moment, the only reference I found while looking it up was an addition to the old Rust book which discussed the possibility of writing multiple “impl” blocks for a given type.

You are right that this is a pretty powerful functionality which violates the naive intuition that many of us have regarding “impl” while learning Rust, and that it should probably be more clearly documented. I will check the details of what the new Rust book currently says about this, and propose some additions if relevant.

EDIT: Relevant Rust book issue created @ https://github.com/rust-lang/book/issues/785