Why am I required to "cover" T in `impl ForeignTrait<LocalType> for T` [E0210]

Viewed 1333

Today I ran into a pretty strange error message that I'm having a hard time understanding. Consider this easy map-entry-like struct:

struct Entry<K, V> {
    key: K,
    value: V
}

Now, I want to implement all the std::cmp traits between Entry<K, V> with itself and with just K. Let's focus on PartialEq for now. These two implementations work just fine:

impl<K: PartialEq, V> PartialEq for Entry<K, V> { /* ... */ }
impl<K: PartialEq, V> PartialEq<K> for Entry<K, V> { /* ... */ }

But the last one gives me a hard time (Playground)

impl<K: PartialEq, V> PartialEq<Entry<K, V>> for K {
    fn eq(&self, other: &Entry<K, V>) -> bool {
        self.eq(&other.key)
    }
}

The error message, as far as I can understand it, claims I'd be using a non-local type as the first parameter of the foreign trait. However, Entry ist defined locally in the same file.

error[E0210]: type parameter K must be covered by another type when it appears before the first local type (Entry<K, V>)

--> src/lib.rs:6:6
  |
6 | impl<K: PartialEq, V> PartialEq<Entry<K, V>> for K {
  |      ^ type parameter `K` must be covered by another type when it appears before the first local type (`Entry<K, V>`)
  |

note: implementing a foreign trait is only possible if at least one of the types for which is it implemented is local, and no uncovered type parameters appear before that first local type
note: in this case, 'before' refers to the following order: impl<..> ForeignTrait<T1, ..., Tn> for T0, where T0 is the first and Tn is the last

Can someone explain why I am getting this error message, what the error and especially uncovered means and why this implementation is disallowed?

2 Answers

The error message is pretty poor but reading the full description of E0210 very carefully gives at least an explanation as to what's going on:

Consider an impl:

impl<P1, ..., Pm> ForeignTrait<T1, ..., Tn> for T0 { ... }

where P1, ..., Pm are the type parameters of the impl and T0, ..., Tn are types. One of the types T0, ..., Tn must be a local type (this is another orphan rule, see the explanation for E0117).

Both of the following must be true:

  1. At least one of the types T0..=Tn must be a local type. Let Ti be the first such type.
  2. No uncovered type parameters P1..=Pm may appear in T0..Ti (excluding Ti).

Now, looking back at the implementation in question, it is declared as

impl<K: PartialEq, V> PartialEq<Entry<K, V>> for K // error

So gives a mapping of P1=K, P2=V and T0=K, T1=Entry<K, V>, Ti is T1 and therefore the error message is triggered. Covering is as easy as putting it into a one-element tuple, so this syntax is totally fine:

impl<K: PartialEq, V> PartialEq<Entry<K, V>> for (K,) // ok

Generally, covering a type can be done by placing it into a tuple ((K,)) or using it as a type parameter (SomeType<K>), but not using a reference (&K) or a box (Box<K>).

An understandable and reasonable explanation for this is then only given in the implementing RFC 2451, which recommended this to be part of the error message:

Rust's orphan rule always permits an impl if either the trait or the type being implemented are local to the current crate. Therefore, we can't allow impl<T> ForeignTrait<LocalTypeCrateA> for T, because it might conflict with another crate writing impl<T> ForeignTrait<T> for LocalTypeCrateB, which we will always permit.

I wish rustc would've adopted that error message rather than spitting out the cryptic one it currently does.

I think you want the other way around:

impl<K: PartialEq, V> PartialEq<K> for Entry<K, V> {
    fn eq(&self, other: &K) -> bool {
        self.key.eq(&other)
    }
}

As for why your direction does not work: Imagine someone defines a struct that can be compared to anything:

struct K;

impl<V> PartialEq<V> for K { // V could be anything (including your struct Entry)
    fn eq(&self, other: &V) -> bool {
        true
    }
}

Now, there would be conflicting impls for K and your Entry.

Related