More surprises with the `==` operator

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]>.

This actually does not fail. I think I mixed something up. I'm sorry. I had a previous version that looked different when it was failing. Maybe I'll figure out again what confused me.


Okay, the surprising thing is this:

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

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: can't compare `[u8; 1]` with `&[u8; 1]`
 --> src/main.rs:6:19
  |
6 |     assert!(array == array_ref); // fails
  |                   ^^ no implementation for `[u8; 1] == &[u8; 1]`
  |
  = help: the trait `PartialEq<&[u8; 1]>` is not implemented for `[u8; 1]`
  = 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

I would expect that if I can't compare [0; 1] with &[0; 1], then I would also not be able to compare [0; 1] with a slice. But comparison with &[u8] works (but not with &[u8; 1]).

That is a bit... strange (but may be just like how the std library works).


It works exactly because of this impl:

So unsized coercion is indeed not involved.

Side question: How can it be existing since Rust 1.0 while it uses const generics? (Might need to look that up in the repository history.)

There used to be a macro that generated the 33 separate impls. It made the docs huge and ugly https://doc.rust-lang.org/1.15.0/std/cmp/trait.PartialEq.html

Those labels of when things were added aren't fully reliable, and don't reflect various important changes (like when no-reallocation guarantees are added). If you really care about what was available in a particular release, the right answer is to look at the docs from that version. (And to build using that version of rust in CI in addition to stable.)

5 Likes

That documentation is counterfactual, and see also #44762.

(I didn't read this thread in depth.)

4 Likes

The analysis above is verbose. Here is a simple workflow:

let array: [u8; 1] = [0u8; 1];
let array_ref: &[u8; 1] = &[0u8; 1];
let slice: &[u8] = &vec![0u8];
assert!(slice == array_ref); // &[u8]: PartialEq<&[u8; 1]> => [u8]: PartialEq<[u8; 1]>
  1. Write down the left-hand-side and right-hand-side types: &[u8] == &[u8; 1]

  2. Turn it into the trait bound form[1]: &[u8]: PartialEq<&[u8; 1]>
    or impl form: impl PartialEq<&[u8; 1]> for &[u8]

  3. search for &[

    impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
    impl<T, U, A> PartialEq<Vec<U, A>> for &[T]
    

    neither are wanted

  4. search for common generic impls and deduce

    impl<A, B> PartialEq<&B> for &A
    where
        A: PartialEq<B> + ?Sized,
        B: ?Sized,
    

    thus we can remove & on both sides: [u8]: PartialEq<[u8; 1]>
    then we'll find impl<A, B, const N: usize> PartialEq<[A; N]> for [B]


  1. Make sure you get the the correct a == b desugaring:
    <TypeOfA as PartialEq<TypeOfB>>::eq(&a, &b) ↩︎

2 Likes

Yeah, I see. It's pretty "verbose" to find the right implementation. Thank you for clearing this up!

It makes sense there is an impl<A, B, const N: usize> PartialEq<[A; N]> for [B] (which then allows us to compare a &[B] with an &[A; N] through the generic impl for references on both sides.

But the surprising thing (for me) is this impl:

PartialEq<&[B]> for [A; N]

Which allows this:

I find it surprising that I can compare an owned/non-reference array [A; N] with a referenced slice &[B]. But maybe it's included in the standard library because it's something handy and doesn't cause problems with overlapping implementations (as some other impls would do).

I noticed this because sometimes, when I manually do an unsized coercion, code will work that didn't work before. For example:

fn main() {
    let array: [u8; 1] = [0u8; 1];
    let array_ref: &[u8; 1] = &[0u8; 1];
    let slice: &[u8] = &vec![0u8];
    assert!(array == slice); // works
    //assert!(array == array_ref); // fails
    assert!(array == &array_ref[..]); // works again
    assert!(array == array_ref as &[_]); // different syntax, also works
}

(Playground)

I believe the reason for not allowing different levels of referencing on a single side of the == operator is due to problems with Rust's type system (overlapping implementatins), and if I understand it right, implementing it for a single case (such as for arrays and referenced slices) causes no problem. Still leaves one with surprises when comparison (or other operations) with different reference levels will work or fail.

But you might be unsurprised at String: PartialEq<&str> and surprised at String: PartialEq<str>:

let s = String::new();
dbg!(s == ""); // works: unsurprised
dbg!(s == *""); // works: surprised

Update: I think it's natural to use the second impl when you have &String == &str:

dbg!(&s == ""); // works

So I suppose the main reason for supporting these symmetric impls

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

impl<T, U, A> PartialEq<Vec<U, A>> for &[T]
impl<T, U, A> PartialEq<&[U]> for Vec<T, A>

impl<'a, 'b> PartialEq<&'a str> for String
impl<'a, 'b> PartialEq<String> for &'a str

is &str and &[T] are so common that you hardly notice that you're comparing an owned type with a referenced type.

let slice: &[u8] = &[1]; // comes from a fn argument or generic as_ref or coercion or whatever
assert_eq!([1], slice); // [1] or vec![1] as an owned type comes from the result of a function/method or a locally initialised variable
assert_eq!(vec![1], slice);
3 Likes

Kind of unsurprised, as I use it a lot. Though now that I think of it, Rust could also require to write &owned_string == string_slice.

But we have also:

fn main() {
    let vec: Vec<u8> = b"binstr".to_vec();
    let slice: &[u8] = &vec;
    assert_eq!(vec, slice);
}

(Playground)

So Rust is consistent here between Vec<T> and String.

I guess it makes sense to implement PartialEq<B> for O where O: Borrow<B>, but I also guess such a blanket implementation would not work due to potentially overlapping implementations (not sure though). Update: See also Playground. Borrow<T> is implemented for every T: ?Sized, so this could not work (with current Rust).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.