Reputation: 539
The following code will not run:
class X{
double? x;
}
mixin Y{
double? y;
}
class Z extends X with Y {
double? z;
Z(this.x, this.y, this.z)
}
The compiler will complain that this.x and this.y are not fields in the enclosing class:
lib/physicalObject.dart:20:10: Error: 'x' isn't an instance field of this class.
Z(this.x, this.y, this.z)
^
lib/physicalObject.dart:20:18: Error: 'y' isn't an instance field of this class.
Z(this.x, this.y, this.z)
^
Of course this is not true, the fields x and y are inherited from the parent class and mixin. I can use these fields within the child class Z, it seems it is only the constructor, which has a problem accepting these as parameters. But why?
Upvotes: 5
Views: 1818
Reputation: 71623
Dart instance variables (aka. "fields") introduce a storage cell, an implicit getter and an implicit setter (unless the variable is final
and not late
, then there is no setter).
The constructor initializer can initialize the storage cell directly, without calling the setter, and even if there is no setter. That's what Z(this.z)
or Z(double? z) : this.z = z;
does, it stores a value directly into the storage of the instance variable.
A constructor like Z(double? z) { this.z = z; }
does not store directly into the cell, it instead calls the setter named z=
which then might store into the (necessarily non-final or late) variable's storage.
Or it might not. Setters are virtual, and a subclass can override them, and the { this.z = z; }
assignment would call the overridden setter.
In general, a subclass does not see the variable of the superclass, all it sees is the interface of the superclass, which exposes only the getter and setter. If the superclass decides to change the field declaration into a getter and setter declaration pair, then they can. It's non-breaking, and the subclass can't tell the difference. So, if the subclass can somehow see that a variable can be initialized (not just assigned to), then we have broken that symmetry, and the superclass is locked in to having an initializable variable.
Initialization must happen in the class which declares the instance variable because if it doesn't, we break the abstraction of that class's interface by leaking whether the getter/setter is backed by a field or not, and we then lock the class into that choice. Which is the totally opposite of why Dart has the kind of getter/setter declarations it has, which is to make it an implementation choice whether you use one or the other, not a public API choice.
Upvotes: 6