Our current best-practice for operator == is:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
final Foo typedOther = other;
return typedOther.bar == bar
&& typedOther.baz == baz
&& typedOther.quux == quux;
}
It's unfortunate that we have to introduce a new variable here. It would be nice if this worked:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
assert(other is Foo); // compiler promotes other to Foo
return other.bar == bar
&& other.baz == baz
&& other.quux == quux;
}
...or maybe with new syntax:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
promote other as Foo; // equivalent of: if (other is! Foo) throw TypeError();
return other.bar == bar
&& other.baz == baz
&& other.quux == quux;
}
...or even:
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) // compiler knows this being false means other is Foo
return false;
return other.bar == bar
&& other.baz == baz
&& other.quux == quux;
}
See also:
I understand that the focus is on equality here, but promotion seems to be an important topic as well.
For that, note that https://github.com/dart-lang/sdk/issues/25565#issuecomment-421914603 lists a number of other issues targeting promotion improvements, and #25565 is in general a good hub for keeping discussions on that topic together.
In the language team we have discussed using an expression statement like other as Foo; to promote other to Foo, and that idea is certainly relevant to the discussion in #25565 (and to proposals like this one).
The last example using other.runtimeType != runtimeType is a smart trick, and it looks really attractive that we might get rid of the type test altogether! ;-)
However, the test other.runtimeType != runtimeType cannot soundly show anything about the type of other, because any class (both that of this and that of other) could override runtimeType to return whatever it wants. We could just ignore soundness and trust the result (and generate dynamic checks to ensure that the heap remains sound at run time), but that would be inconsistent with all recent developments in the definition of Dart. We could make it safe by introducing a Type.runtimeTypeOf(Object) static method, which would be guaranteed to return the _actual_ run-time type of its argument. But even with that, it would still be a somewhat odd and ad-hoc promotion, because subtypes of the run-time type of this would be rejected, even though they would succeed at an as Foo or is Foo test.
We could also just ban overrides of runtimeType. Combine this with proposals elsewhere to add post-fix "fake methods" like .as, and define .runtimeType as one of these fake methods. Or combine it with the proposal to introduce sealed methods, and seal it. Or just special case runtimeType.
Can we update the first post from "Our current best-practice" to "Flutter's current best-practice"? Using runtimeType for equality checks is not a universal best practice.
I do think it would be worthwhile to explore disallowing overrides of runtimeType.
Going further, Flutter SDK's best practice. Most Flutter users do not use this pattern.
Would it not be valid to write the following? Short-circuiting at the type-check seems to protect against illegal accesses.
@override
bool operator ==(other) =>
other is ThisClass
&& other.bar == bar
&& other.baz == baz
&& other.quux == quux;
As a Swift developer, I love that the Swift compiler synthesizes Hashable and Equatable for simple structs. Seeing that in Dart would be great (and while I'm at it, the first-party Codable support).
Using is in operator == is usually wrong as it makes the == operator asymmetric.
Using
isinoperator ==is usually wrong as it makes the==operator asymmetric.
Was my example asymmetric? I think this is the kind of code most people will want to write, now I'm doubting it. :)
consider
class ThatClass extends ThisClass {
ThatClass(this.bob, Bar bar, Baz baz, Quux quux) : super(bar, baz, quux);
final Bob bob;
operator ==(other) =>
other is ThatClass
&& other.bob == bob
&& super==(other);
}
// ...
print(ThatClass(a, b, c, d) == ThisClass(b, c, d)); // false, good
print(ThisClass(b, c, d) == ThatClass(a, b, c, d)); // true, oops