Strange coercion during comparison

Consider this:

fn main() {
    let v = vec![2,3];
    let n = vec![2,3];
    let s = n.as_slice();
    //println!("{}", s == &v);
    //println!("{}", v == &s);
    //println!("{}", &s == v);
    //println!("{}", &v == s);
    //println!("{}", &s == &v);
    println!("{}", &v == &s);
    println!("{}", s == v.as_slice());
}

All non-working lines have been commented out, only the bottom two is accepted by the compiler.

However I don't quite understand why some of the commented lines is failing.

For example, the first one: s == &v

s is of type &[], and I guess &v would be coerced into the a slice to match.

To my surprise, the error message is: no implementation for [{integer}] == std::vec::Vec<{integer}>, It seems that both s and &v are implicitly stripped of &, instead of &v being coerced.

Another example is I cannot figure out why &v == &s works but &s == &v doesn't.

Thanks!

1 Like

The operands aren't coerced here; coercion happens only when the “expected” type is a single type. Since PartialEq<T> may be implemented by multiple types, the compiler doesn’t know what to coerce to.

The full error message for this case is:

  = help: the trait `std::cmp::PartialEq<std::vec::Vec<{integer}>>` is not implemented for `[{integer}]`
  = note: required because of the requirements on the impl of `std::cmp::PartialEq<&std::vec::Vec<{integer}>>` for `&[{integer}]`

Since both types are references, the compiler finds this impl from the standard library and tries to apply it:

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

but it fails because the A: PartialEq<B> bound isn’t met, which is what the first part of the error message is telling you.

Vec<T> implements PartialEq<&[T]>, but &[T] does not implement PartialEq<Vec<T>>. This is because the slice type is in libcore while Vec is in libstd. Impls in libcore can't reference types in libstd.

4 Likes

Great answer! Thank you very much!

Things I learned from your post:

  1. The trait PartialEq can be impl'd asymmetrically. If I only impl PartialEq<Bar> for Foo, but not otherwise, then Bar == Foo is invalid; playground

  2. There is a blanket impl PartialEq<&B> for &A where A: PartialEq<B> which basically means to write &A == &B, A must impl PartialEq<B>;

  3. &v == s is not valid because Vec<T> does not impl PartialEq<[T]>. (Why?) There is a similar looking trait impl PartialEq<[B; N]> for Vec<A> but it's for arrays, not slices.

  4. I guess the simplest way to to write the comparison is v == s. Because Vec<T> implements PartialEq<&[T]>.

  5. "Impls in libcore can't reference types in libstd." I guess this is because the "nostd" situation in embedded development. Is this correct?

I'm not sure! Possibly just an oversight. I found that there is an open RFC issue proposing more Vec/slice impls, and I posed the question there.

At some level, yes. More directly, it's because libraries in Rust can't have circular dependencies: std depends on core, so core must be compiled before std and can't depend on std.

However, it looks like recent changes to the rules about impls have made it possible for these missing impls to exist in libstd rather than libcore. There is a proposal to add them, in that RFC issue (above).

2 Likes

I'm surprised you didn't try the simplest possible comparison:

    println!("{}", v == s);
    //println!("{}", s == v); // doesn't work for the same reason as &s == &v

Since &[T] and Vec<T> both dereference to [T], you might also use * to compare them:

    println!("{}", *v == *s);
    println!("{}", *s == *v); // also works because both *s and *v are [i32]

Comparison operators (==, !=, >=, <=, >, and <) implicitly reference their arguments, so *v == *s desugars to something like <[i32] as PartialEq>::partial_eq(&*v, &*s). Nothing is being moved here.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.