Why doesn't this code compile?

I have this snippet which fails to compile:

trait TokenStream<I>: IdentSource {
    fn next(&mut self, interner: &mut I) -> Token<Self::Ident>;
}

struct Token<I>(I);

trait IdentSource {
    type Ident: Clone + std::fmt::Debug + PartialEq + AsRef<str>;
}

type MyAlias<I> = Vec<Box<dyn TokenStream<I, Ident = String>>>;

fn main() {
    let i = MyAlias::<String>::new();
}

However, if I change IdentSource::Ident's type bound to include Eq instead of PartialEq then it compiles. I can't figure out what's wrong with the original bound though.

If you want to use trait objects to erase a type at runtime, the language has to enforce several restrictions.

The one you are running into is that the std::cmp::PartialEq trait is generic over the item on the right side of the ==, and by default PartialEq is short for PartialEq<Rhs = Self>. This is a problem when you create a trait object because how can we erase an object's type at runtime, while also requiring that it is equatable with its original type (which we no longer know/have access to)?

I'd drop the PartialEq requirement from Ident and just do all equality checking via the string returned by AsRef<str>. The reasoning is that when comparing two identifiers I don't necessarily care what type of identifier the human wrote, just that their string representations are identical.

But Eq, which does work here, implies PartialEq, so how come it works? Also, the type alias specifies the exact type of Ident, so it shouldn't be a problem for type erasure.

Eq requires PartialEq<Self> because total equality by definition means equality within the same type. Therefore, it's no longer generic.

But PartialEq is the same as PartialEq<Self>, so it's fully specified.

This is bug #65078. Looks like you can work around this by creating a trivial subtrait of PartialEq

trait PartialEqWrapper: PartialEq {}
impl<T: PartialEq> PartialEqWrapper for T {}

and bounding the associated type with that. Playground link

4 Likes