Warning: you need to understand metatables and, possibly, IEEE754 arithmetics to understand this post. If you don’t, just skip it
In Lua 5.1, the semantics of the equality checks (
~=) are such that when an object that’s being compared has an
__eq metamethod, the metamethod is only called when the object isn’t compared with itself. In other words,
a == b works approximately as
rawequal(a, b) or getmetatable(a).__eq(a, b). This means that
__eq isn’t called when you compare an object with itself.
This has been true of Roblox Lua implementation as well, but accidentally was changed in Luau when it was released in 2019 for a particular case of builtin userdata comparison - for
CoordinateFrame and other objects like that
v == v was in fact calling the built-in
This is significant as since 2019, games started using this to compare
Vector3 objects to itself to check for NaN values (
v ~= v is true if any component of the vector is NaN). You may think this is a hack, but it actually makes sense - the following code always was a correct way to check if any component of a
Vector3 is a NaN:
a.X ~= a.X or a.Y ~= a.Y or a.Z ~= a.Z
If you’re confused by this, it’s alright - NaN semantics can be confusing! But very smart people in 1985 decided that this is a good idea, and pretty much all computers and programming languages today implement this semantics for floating-point computation.
This brings us to:
As of today, we’re making this behavior official and consistent. Instead of only calling
__eq for self comparisons in rare cases, we’re now always calling
__eq even if the object is compared with itself.
Why is it a good idea?
This change removes the difference in semantics between builtin types and custom table-based types. This means that if you were to implement a
Vector5 type by using a table with a metatable, you’re going to get the same behavior wrt equality comparison as
Vector3. This makes builtin types less special, which is great.
It also keeps the existing games working. We initially wanted to revert the behavior back to be in line with Lua 5.1, but existing games had code that wasn’t compatible with this change. And the existing situation was very inconsistent, so we had to change it one way or the other - this way should be less disruptive than alternatives while still resulting in a consistent comparison behavior.
It also just makes sense. For example, Lua 5.1 won’t call
__eq when you ask it
v == v, but will call
__le when you ask if
v <= v. It makes more sense to treat all types and operators uniformly without magical behavior.
This change is effective as of April 9th.
Will my game still work?
… almost definitely.
We’ve had some reports of games that may be affected by this when they incorrectly implemented
__eq metamethod but the problem happened to never occur.
For example, consider this code:
getmetatable(t).__eq = function (a, b) return a == b end
This code is definitely not really necessary, but is it correct? The answer is - no! The
== operator inside the
__eq implementation will just call
__eq again, resulting in a stack overflow. The correct implementation here is
return rawequal(a, b).
However, if the code above was never called - which could happen if you only ever compared objects of this type redundantly to itself - the error could have been hidden in plain sight, and now it’s visible - because before this change we wouldn’t call the
__eq when you compared an object of this type to itself (
t == t), and now we will.
Please let us know if there are other unintended cases like this; we currently believe that all correct code is not affected. Needless to say, if your game never implements
__eq it is not affected by this at all.