More surprises with the `==` operator

Continuing the discussion from Surprising: &1 + &1 == 2:

More surprises:

fn main() {
    let array: [u8; 1] = [0u8; 1];
    let array_ref: &[u8; 1] = &[0u8; 1];
    let slice: &[u8] = &vec![0u8];
    assert!(slice == array_ref); // works
    //assert!(array_ref == slice); // fails
    assert!(slice == array); // works
    assert!(array == slice); // works
    //assert!(0 == &0); // fails
}

(Playground) Edit: I made a mistake in my playground, see this post.

Why does slice == array_ref work while array_ref == slice doesn't compile? And why can I compare a slice (reference) with an (owned) array value while integers and references to integers don't compare?

P.S.: And why does array == slice work while array_ref == slice doesn't?

It all boils down to what PartialEq impls are available in the standard library. There's no deep insight to be had or anything interesting here, really – T == U compiles if and only if impl PartialEq<U> for T exists. This is not symmetric, so it doesn't automatically imply U == T compiling.

8 Likes

Oh, I thought that unsized coercions are involved somehow. But looks like since const generics are stable, there are implementations of PartialEq for arrays (with generic length).

But I don't find any implementation PartialEq<&[A; N]> for &[B], which would be needed for slice == array_ref. Or did I miss it somewhere? So I guess unsized coercions are involved somehow?

Another question: How can the standard library have these implementations since Rust 1.0.0 whereas const generics weren't available back then?

It's here.

impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
where
    B: PartialEq<A>,
1 Like

That's distinct from PartialEq<&[A; N]>.

Note the generic parameter Rhs in pub trait PartialEq<Rhs = Self> doesn't need to be a reference, since the reference happens in methods:

fn eq(&self, other: &Rhs) -> bool;
fn ne(&self, other: &Rhs) -> bool { ... }

Edit: I made a mistake too[1]. The impl should be this:

impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
where
    B: PartialEq<A>,

  1. not PartialEq<[A; N]> for &[B] â†Šī¸Ž

2 Likes

IIRC coercions aren't applied to generics (that'd be way too magical and confuse inference as well). See e.g.

1 Like

I think the == operator turns the LHS and RHS into a reference already. See comparison operators in the reference:

a == b;
// is equivalent to
::std::cmp::PartialEq::eq(&a, &b);

Thus the & in the trait method definition is already needed due how the == operator works.

x.eq(y) can also be written x == y , and x.ne(y) can be written x != y . We use the easier-to-read infix notation in the remainder of this documentation.
src: PartialEq in std::cmp - Rust

I believe that section in the documentation is wrong. It should read the following in order to be consistent with the Rust reference:

x.eq(&y) can also be written x == y , and x.ne(&y) can be written x != y.

Or is the Rust reference incorrect?

But then what's the problem? That is exactly why you don't need to put the reference into the type parameter itself – the method already takes &self and &Rhs, not self and Rhs.

&a == &b would translate into (&a).eq(&&b)

No, it would translate into (&&a).eq(&&b) (method calls auto-ref!), and there's an impl PartialEq<&U> for &T, which removes one level of reference from both sides.

It makes sense to me, too :slight_smile:

The best way is to use fully qualified syntax instead of method call syntax (due to auto-ref and auto-deref) in the doc.

struct Bar;

#[derive(PartialEq, Eq)]
struct Qux;

impl PartialEq<Bar> for Qux {
    fn eq(&self, _other: &Bar) -> bool {
        true
    }
}

let bar = Bar;
let qux = Qux;
dbg!(qux.eq(bar)); // This doesn't work!

error[E0308]: mismatched types
  --> src/main.rs:19:17
   |
19 |     dbg!(qux.eq(bar)); // won't work
   |              -- ^^^
   |              |  |
   |              |  expected reference, found struct `Bar`
   |              |  help: consider borrowing here: `&bar`
   |              arguments to this function are incorrect
   |
   = note: expected reference `&_`
                 found struct `Bar`

Okay, to be precise, let's say it translates to ::std::cmp::PartialEq::eq(&&a, &&b);, and yeah one level of references get removed though the generic impl. So it calls ::std::cmp::PartialEq::eq(&a, &b);.

My point is that the the &'s in the eq method definition (both on &self and on the RHS) correspond to the &'s added by the == operator, which is why I found this misleading:


I.e. for a == &b to work, I need an impl PartialEq<&TypeOfB> for TypeOfA, and not an impl PartialEq<TypeOfB> for TypeOfA. (see Playground Playground)

That's true. So let's agree on this:

a == &b desugars to <TypeOfA as PartialEq<&TypeOfB>>::eq::(&a, &&b)

i.e. a == b desugars to <_>::eq::(&a, &b)

So the OP assert!(slice == array_ref); // works is due to <_>::eq::(&slice, &array_ref). Let's find out:

let array_ref: &[u8; 1] = &[0u8; 1];
let slice: &[u8] = &vec![0u8];
assert!(slice == array_ref); // works
// <_>::eq(&slice, &array_ref); // error[E0034]: multiple applicable items in scope
assert!(<[u8] as PartialEq<[u8; 1]>>::eq(&slice, &array_ref));

That's because this works too:

assert!(<[u8] as PartialEq<[u8; 1]>>::eq(&slice, &&&&&&&array_ref));

(Playground)

But if you add a & in the type parameter of PartialEq, you will see that the implementation does not exist:

fn main() {
    let array_ref: &[u8; 1] = &[0u8; 1];
    let slice: &[u8] = &vec![0u8];
    assert!(slice == array_ref); // works
    // <_>::eq(&slice, &array_ref); // error[E0034]: multiple applicable items in scope
    assert!(<[u8] as PartialEq<&[u8; 1]>>::eq(&slice, &array_ref));
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: can't compare `[u8]` with `&[u8; 1]`
 --> src/main.rs:6:55
  |
6 |     assert!(<[u8] as PartialEq<&[u8; 1]>>::eq(&slice, &array_ref));
  |             ---------------------------------         ^^^^^^^^^^ no implementation for `[u8] == &[u8; 1]`
  |             |
  |             required by a bound introduced by this call
  |
  = help: the trait `PartialEq<&[u8; 1]>` is not implemented for `[u8]`
  = help: the following other types implement trait `PartialEq<Rhs>`:
            <&[B] as PartialEq<[A; N]>>
            <&[T] as PartialEq<Vec<U, A>>>
            <&mut [B] as PartialEq<[A; N]>>
            <&mut [T] as PartialEq<Vec<U, A>>>
            <[A; N] as PartialEq<&[B]>>
            <[A; N] as PartialEq<&mut [B]>>
            <[A; N] as PartialEq<[B; N]>>
            <[A; N] as PartialEq<[B]>>
          and 3 others

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

These all work.

// <[u8] as PartialEq<[u8; 1]>>::eq(&[u8], &[u8; 1])
assert!(<[u8] as PartialEq<[u8; 1]>>::eq(&slice, &array_ref)); // [coercion] &slice: &&[u8] -> &[u8], &array_ref: &&[u8; 1] -> &[u8; 1]

// <&[u8] as PartialEq<&[u8; 1]>>::eq(&&[u8], &&[u8; 1])
assert!(<&[u8] as PartialEq<&[u8; 1]>>::eq(&slice, &array_ref)); // satisfy 

// <&[u8] as PartialEq<[u8; 1]>>::eq(&&[u8], &[u8; 1])
assert!(<&[u8] as PartialEq<[u8; 1]>>::eq(&slice, &array_ref)); // [coercion] &array_ref: &&[u8; 1] -> &[u8; 1]

I don't think it's misleading, you are misinterpreting the answer. It is true that the RHS type doesn't have to be a reference. If you want to compare T and U, then neither T nor U has to be a reference. It is not the case that a type must be a reference in order to be comparable for equality.

What is true is that the LHS and the RHS have to be the same as the Self and Rhs types being compared on the two sides of the == sign. Thus, if you want to compare e.g. Foo with &Bar then there has to be an impl PartialEq<&Bar> for Foo. But this has nothing to do with "references". This is just how the == operator is desugared.

1 Like
// <[u8] as PartialEq<&[u8; 1]>>::eq(&[u8], &&[u8; 1])
<[u8] as PartialEq<&[u8; 1]>>::eq(&slice, &array_ref) // [coercion] &slice: &&[u8] -> &[u8], 

But by searching for for [B] on PartialEq in std::cmp - Rust webpage, you can find only one impl

impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
where
    B: PartialEq<A>,

Thus you don't have [u8] as PartialEq<&[u8; 1]>.