New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A post constructor call-back (finalizer) would be a nice addition #3745
Comments
I'd rather give mixins constructors. Something like #1605 (comment) That's how you initialize an object. Doing it after the object has been created risks a worse design (fields not |
Ok I elaborate on why I think a constructor isn't able to do the same mixin mixinA {
late int i;
void init() {
i = getValue();
}
int getValue();
}
class A with mixinA {
A() {
init();
}
@override
int getValue() {
return 10;
}
}
class B extends A {
B() {
init();
}
@override
int getValue() {
return 220;
}
} As we have no way to pass values from a class constructor to a mixin the only way to get values into a mixin is by calling an The problem however is that this init function has to be called somewhere and depending on in which constructor of inheriting classes it is called the mixin might receive different values. Following the idea that always the overridden operations are the ones that are called unless they include a call to If the With a This would also allow base classes to be initialized by calling overridden functions/properties of derived class instances which is currently not possible while guaranteeing that the constructor of the derived class has been called before. As finalizers should always only update the class where they are defined the order in which they are called doesn't matter. The argument about final is the same if you try to assign final variables inside a constructor now, it's not possible, which I find is a flaw as it requires them to be made late. We could imagine allowing write access to final properties once either inside the constructor or a finalizer. Overall a |
If we add constructors to mixins, you would also get a way to call them (and probably be required to call them) in the subclass constructor. Obviously if getting the value to initialize with is an instance method call on the same object, you can't do that until after initializers have run. First of all, today I'd write the example above as: mixin mixinA {
late int i = getValue();
int getValue();
}
class A with mixinA {
@override
int getValue() => 10;
}
class B extends A {
@override
int getValue() => 220;
} It won't initialize the field as eagerly, but it's a late field, so that shouldn't matter. If I had mixin constructors (the way I envision them, which I think is in the link I gave), I'd probably write it as: mixin MixinA {
int i;
MixinA(this.i);
}
class A with MixinA {
A() : this._(10);
A._(int i) : MixinA.super(i), super();
}
class B extends A {
B() : super._(220);
} or something like that. If you are designing the classes together, you can use private constructors to hide the value passing. If you want to keep the current behavior as close as possible, then maybe I'd write it as: mixin MixinA {
late int i;
MixinA() {
i = getValue();
}
int getValue();
}
class A with MixinA {
// Implict default constructor in the presence of mixins becomes:
// A() : MixinA.super(), super();
@override
int getValue() => 10;
}
class B extends A {
// Implicit default constructor is the usual one:
// B() : super();
@override
int getValue() => 220;
} The body of a mixin constructor is a function body that gets called after the object was created, before the object construction expression returns.
That's not how constructor bodies work in Dart.
This is Dart, not C++ or Java. If you have access to the object, through Generally when the constructor body of class |
But when an instance of B is created, if the ctor of A is calling ´getValue()´ it will call B.getValue but at this point, the ctor of B hasn't run yet, so B might not have been fully initialized. Actually by pointing out the use of a ´late´ property inside the mixin to trigger an init function that would allow to have an init function being executed after its instance has completely be created (given that that property isn't accessed by any ctor) solves what the |
True. If the Dart constructors have two phases: Subclass-to-superclass initialization in the initializer list, superclass-to-subclass post-initialization operations in the body. You're asking for a third phase, run after all bodies have executed. I don't see that happening. If your object initialization is so complicated that it needs three phases, you need to either reconsider some choices, or introduce an abstraction (say, only allow construction through a factory, which calls the constructor and the post-construct-setup before returning the object.) It's a program design issue, suggesting that the modularization boundaries are not optimal, when information has to pass both ways between a superclass and a subclass. That is not something that I'd want to complicate the language with, adding a third phase to all constructor invocations, because the wast majority of classes do not have the problem, or have found ways around it. Consider something like: mixin MixinA {
late int value;
void _init() {
value = getValue();
}
int getValue();
}
class A with MixinA {
final String something;
A._(this.something);
A(this.something) { _init(); }
int getValue() => 10;
}
class B extends A {
late int _theValue;
int getValue() => _theValue;
B._(super.something) : super._();
B(super.something) : super._() { _theValue = 220; _init(); }
} That is: Public constructors that call the inherited This ensures all object constructions call (Or alternatively, rather than putting the There are designs that give you what you want, but it is a complicated thing that you want, and I'm OK with it requiring some design work. I don't see it as reason enough to add a langauge feature. |
Looking at how complex the solution above looks like, compared to an additional third step that calls a clearly defined |
Sure, a third phase will solve this problem. Until someone comes up with a problem that requires a fourth phase. We're past creating objects, a point we passed the moment we started executing bodies, because at that point the object was initialized as far as the language is concerned. We're well into implementing protocols for classes to communicate with each other up and down the subclass chain. There is no end to the possible complexity of that, which is why it's something to handle in code, not by adding specialized language features for each possible complication. The example here is so simplified that I can come up with many different designs that solve the same issue. In a real situations, the logic to initialize the field is probably more complicated and local to the mixin. It just needs some values. And it's too soon to call In many cases, you don't call And we're back to it being so complicated that it needs to be solved in code, not language features, because there is not one size that fits all. And another design, while I'm here: mixin MixinA {
int get value;
}
class A with MixinA {
int get value => 10;
}
class B exends A {
late int value;
B() { value = 220; }
} That is: Why does the mixin have to initialize a field, why can't it just define the field and let subclasses initialize it? |
Because, that way the mixin can't enforce that subclasses will initialize it. By defining abstract getters it can enforce subclasses to implement them. I can solve that by checking inside the properties of the mixin if it has already been initialized and do the initialization if needed. But again that is not an expressive way to do it. (that is the same for any super class) honestly I feel you are getting a bit polemic with your argument that the next one will require a 4th phase. I laid out why a third phase makes sense. A 4th doesn't make any sense. |
I can see that a third phase here will solve your problem. I don't necessarily believe that your problem is common enough that it warrants a language-based solution. I'm also not sure that if it does, adding a third phase to constructor invocations would be the solution I'd go for. To be concrete:
Let's design this around constructors, as a genuine third phase of constructors:
Here an object creation expression of
This works, but it's highly tailored to the specific problem, and it's not obvious that it generalizes to many other cases. All this because of a strong dependency between superclass and a subclass. Something that could perhaps be avoided by restructuring the code, or just by requiring the user to call You can't do everything in the world inside a constructor. Its job is to initialize the object, synchronously, to a point where the object can safely be provided to others. If the constructor cannot get to that point, because it needs to do more things after the object has been fully created, then exposing a generative constructor might just not be the correct model. /// Classes mixing in this mixin must have `init` called after object creation.
mixin MixinA {
late int i;
void init() {
i = getValue();
}
int getValue();
}
abstract base class ABase with MixinA {
ABase();
@override
int getValue() {
return 10;
}
}
final class A extends ABase {
A() {
init();
}
}
abstract base class BBase extends ABase {
BBase();
@override
int getValue() {
return 22 * super.getValue();
}
}
final class B extends BBase {
B() {
init();
}
} This design ensures that nobody can create an instance of (In general, I discourage extending concrete classes, precisely because it makes initialization ambiguous. A concrete class needs its initialization to create a fully initialized object, but a base class should be ready for subclass initialization to do something afterwards. It's not always reasonable for a class to be prepared for both, and a concrete class may want to only expose factory constructors precisely because it wants to do something after object creation, something that an abstract class doesn't need. There are exceptions - mainly data classes taht are so primitive that the concrete class will never do anything extra. But for complicated classes, I'd say including widgets, combining a base class and a concrete class into one is setting yourself up for trouble.) You may not like this (I don't particularly like it), but given the constraints, it's well within the acceptable design space. I don't see us adding a language feature just to avoid this kind of complication, if the problem to solve is strong temporal dpendencies between superclasses and subclasses that go counter to the execution order of constructor bodies. That just suggests to not use constructor bodies for the job. |
ok, not sure if this really is such a rare situation. My goal was mainly to guarantee that the init function will get called at the right moment and can't be forgotten. |
Would a more general concept like #2831 help you in this example? |
Not sure but I'm thinking, what about a new attribute like |
just saw that there is indeed a |
While adding some mixins to our project I realized, that quite often you want to have some initialization action run on the mixin or on a parent class that expects that some of its members will be overridden by inheriting classes. currently, the only way is to ensure that the class whose constructor is executed last, has to call an initfunctions on parent classes/mixins.
So it would be nice if classes could have a
finalizer
function and the compiler ensures that all finalizers are called once the object is fully created (all constructors are executed). that would make mixins safer to write as you don't have to rely on anyone calling its initfunctionThe text was updated successfully, but these errors were encountered: