Why is `PartialEq` not implemented for `Option`s/`Result`s of differing inner types, where the inner types are `PartialEq`?

To me it seems logical that Option<T> and Option<U> should be comparable if T and U are comparable (i.e. impl PartialEq<U> for T). However, it seems that Option<T> can only be compared with Option<T>.

Similarly, Result<T, E> can only be compared with Result<T, E>, and not some other Result<U, F>, where T: PartialEq<U> and E: PartialEq<F>.

Is there a reason for this restriction?

Unfortunately, this breaks code like if x == None or if x == Ok(0), because the type of the right-hand side can no longer be inferred. See some more discussion and links here:

This is part of the motivation for the new Option::contains and Result::contains methods:

6 Likes

Thanks for the relevant discussion links. I wonder if this is tangentially related to specialization: Rather than selecting from a list of available impls for a type, it would require selecting an available type for an impl...

I have this gut feeling (but no proof sadly) that in the case of eg x == None and x == Some(0) that rustc should be able to infer the type argument for None literal given the context of an equality check. That would work something like "I know x has type Option<u8>, and the RHS type is indeterminate so any fillable holes like the 0 literal can just be type-matched" i.e. use the LHS type to infer the RHS type.
Of course that leads us to a thorny question: exactly how special-cased is such behavior, and (how far) could it be generalized before running into annoying boundaries like the Halting Problem?

You can always create a new type Y and implement PartialEq<Y> for u8. x == None will always be ambiguous because the type of the right side could be Option<Y>.

But that's my point, the literal could be used as a wildcard value so that it would be valid to say None is a value of type Option<X> but it would be equally valid to say None is a value of type Option<Y>. In other words, None is a value of an infinite set of types. Rustc could leverage that to say "well if I just say that this enum variant literal is this type, then the analysis checks out". In other words, it could infer the type.

If there are two possible types, the compiler should throw an error. Inferring some random type in this situation would be a huge footgun.

It's not so different from pattern matching:

if let None = x { ... }
if let Some(0) = x { ... }

Both of these work and are not considered huge footguns, despite the fact that they do something (conceivably, if not in practice) quite different from if x == None.

Because there's no type ambiguity here: if the type at the right is known - so is the one on the left, if the type at the right is unknown - bail out.

Right, my understanding of @jjpe's suggestion is to allow the compiler to resolve the "unknown" type to be the same as the "known" type, in the same way that pattern matching can match the types. Not that the compiler can pick any arbitrary type for which the known type happens to satisfy PartialEq, which would indeed be a footgun.

I can't think of any particularly foot-shooty ways to use the "infer the types to be the same when one is ambiguous" version, but it's possible I merely lack imagination.

(One complication is that it's possible for T to be PartialEq<U> but not PartialEq<T>, in which case I would assume type inference would continue to fail.)

2 Likes

This is indeed the gist of my suggestion.

It's entirely possible for some T and U, that T: PartialEq<U>, but T: !PartialEq<T>. Should this be an error, or should the compiler infer unknown side of equality to be U?