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
}
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.)
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.)
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.
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
}
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.
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);
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).