I’ve been working on a change to how we handle binary operators in the compiler. It turns out that the current code is making too many assumptions baked in from the old days when we didn’t support overloading. Basically, right now we are saying that if the type of the LHS is a builtin integer, like i32, then the type of the RHS must match. This leads it to accepting too much and too little. I’ve experimented now with several approaches to fixing this problem and I wanted to summarize briefly what I tried and try to get people’s opinion about what behavior to encode.
The most conservative – and simplest – version that I tried requires that both the LHS and RHS must have “known” types. This means that they cannot be pure type variables (e.g., the result of
Zero::zero). The reason for this it that it allows the compiler to decide whether this use of
+ is a “builtin” operator (e.g. i32+i32) or one that requires an impl (e.g. i32+MyInt). Requiring that the types be known does break some code though, mostly generic code that is using
Zero::zero and other such operands. The fix is to use UFCS form. I personally find the resulting code easier to understand, but YMMV. You can see the affect on the stdlib and rustc in this comment.
We can fix these cases by taking a different approach (which I also implemented, or mostly). In the other approach, we don’t require that types are known when we type-check
+; instead we defer the work and figure out the types at the end (this requires some special plumbing to make type inference work out, but it’s not that imp’t). This means that those examples above continue to type-check. There are still some bits of existing code that will not work in the new approach, but they are largely constructs that weren’t supposed to work in the first place, or nonsense like
1 + return 22.
However there is a catch. The more permissive approach doesn’t work with the current (unstable) SIMD support, because the result of
i32x4==i32x4 doesn’t have type
PartialEq requires, but rather
i32x4. This is also related to RFC 1008, which would like to make the return type more general. The problem is that even in the more permissive approach, we assume that we know the return type of
a == b (
bool). This assumption is not backwards compatible. If we required more type info up front, we could in principle in the future change the behavior to “try”
PartialEq first and fallback to
PolyEq or whatever the more complex variant is, though I’m not sure we’d ever want to do this – and this sort of “try and fallback” stuff can be unreliable, so if we did want to generalize
==, I might prefer to do via a distinct operator anyhow.
Anyway, I’m feeling a bit torn on which approach to take.