-
Notifications
You must be signed in to change notification settings - Fork 214
lazy keyword: it is a final that is bound to the constructor #1236
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
Comments
Dart currently distinguishes two phases of initialization of a new object instance: Before constructor bodies and after that. After that phase, it's possible to refer to the object through a reference, and we give up all attempts to figure out whether that happens, so at that point all non-late final variables must have been given a value. Dart does not allow you to see a final fields in an uninitialized state. The That's what the initializer list is for. Your code can be written; class Foo extends Disposable {
final StreamController _ctrl;
Stream someInfiniteStream;
Foo() : _ctrl = controller(); { // This will be closed in dispose()
each(someInfiniteStream, (item) => null); // the listener will be cancelled in the dispose()
}
} So, no |
I do think it's interesting to think about what we can do for the cases where the initializer list can't be used. This definitely comes up. I've encountered this same issue: I need to use |
class Foo {
int method() => 5;
final int bar;
Foo() : bar = method() {}
}
void main() {
}
|
Indeed. Something to flag for public late fields. |
A public Regarding |
I am not sure if that issue would solve this and also not sure if extending the initializer lists capabilities to be able to access the class methods could harm optimizations |
The proposal here is to have a way to set the field without a setter, one which is only usable inside the constructor body (and maybe/maybe not inside nested functions). It's not a setter because it's not part of the interface. A private setter could have the same effect, and would allow other code in the same library to cooperate about the initialization. The obvious solution is the classical "private field, public getter" combination. Maybe if there was a way to give implicit getters and setters different names (say, one private and the other public). Strawmanning: int x: { get; _set; } // meaning a field with an implicit public getter, `x`, and an implicit private setter `_x=`. Maybe we should just allow naming them explicitly: int {get x, set _x} = 24;
int {get foo, set bar}; // if you *really* want it.
late final int {get y, set _y; }; // works for any variable declaration. So, where the name would otherwise occur in a (non-local) variable declaration, you can write a name declaration block: |
It looks like the original example could be expressed as follows: class Foo extends Disposable {
late final StreamController _ctrl = controller(); // This will be closed in dispose().
late Stream someInfiniteStream; // Some initialization assumed, not shown.
Foo() {
each(someInfiniteStream, (item) => null); // The listener will be cancelled in dispose().
}
} The difference between using laziness ( With lazy fields the ordering is implicit, more declarative, but may enter an infinite loop; that may be considered an improvement, at least as long as it is so simple that termination is obvious (whatever that means, YMMV ;-). |
For me, a language is just how I express myself to a compiler, be it JS, ByteCode, Assembly etc. "Dear compiler, this property, independently if private or public, is final. |
"Dear Jodinathan. The problem here is that if the field is not set when the constructor body starts running, then most optimizations that you can do with final fields are off the table. Being inside the constructor body is not magical in any way, you're already past the magic window in time where the object is safe from snooping and manipulation. (That said, there aren't that many optimizations of final instance fields anyway, because a subclass might override the field with a getter. You need a whole-program analysis to make sure that doesn't happen.) |
ok. So even with no optimizations at all, it still has no setter which makes code less prone to error. |
I too often wish I could initialize final variables in the constructor. ´late´ has the problem that it only works with nonnullables. But what if the initialization depends on some optional parameter? |
This is not true, you can have a nullable late field.
|
@leafpetersen Ah, that is interesting, I thought it wouldn't make sense to have late with nullable fields. |
With a |
would it be possible for the compiler or linter to check if any ´final late´ aren't initialized at the end of the constructor? |
It is probably not a good idea to enforce that every Also, But you could add a list literal evaluating each of your late final variables (like |
@eernstg Du you think it would be possible to implement a linter for that? |
It is certainly possible to implement a lint that checks at each return statement and at the end of the constructor (if it is reachable) that every late final instance variable of the enclosing class isn't definitely unassigned, or that it is definitely assigned. It may not be so easy to implement, however, because this kind of analysis is currently only performed for local variables. |
That is not always the case! 1. Mixins can not have constructors 2. Instance members can't be used in initializers
There is a very problematic consequence: it's currently not possible to implement immutable Mixins with memoisation. Eg, the above Foo, needs to be
"layz wrapBar" would be a way to get past initializer limitations and currently there is no adequate alternative! |
@jodinathan, the original issue is handled by using a class Foo extends Disposable {
late final StreamController _ctrl = controller(); // This will be closed in dispose().
late Stream someInfiniteStream; // Some initialization assumed, not shown.
Foo() {
each(someInfiniteStream, (item) => null); // The listener will be cancelled in dispose().
}
} I think it would make sense to close this issue. Please create a new issue if the @TheKashe, maybe you could create a new issue with a more complete version of your example? Currently, it is not obvious (to me, at least) how to interpret the code that you mention. class Foo {
computeBar(someParam) {...}
final wrapBar => wrapFunc(computeBar);
} is a syntax error. One way to repair it would be class Foo {
computeBar(someParam) {...}
T get wrapBar => wrapFunc(computeBar);
} where the return type class Foo {
computeBar(someParam) {...}
late final wrapBar = wrapFunc(computeBar);
} which is the solution that I mentioned earlier. In any case, it would be helpful to get some more details about the situation that you're focusing on, and about any proposals for how to handle it. |
@eernstg we were just finishing a project in TS and I remembered this topic. |
There have been many discussions about restricting the possible actions taken by code in a constructor, e.g., that there should be a marker which could be placed somewhere in the list of statements in a constructor, and it would be a compile-time error to do certain things before that marker. If we had a mechanism like that then it could make sense to consider a phrase like
as a useful language rule. However, there are many reasons why Dart does not have a mechanism like that (I can't immediately find an issue specifically on this topic, but I know it has been debated again and again), and this means that there is no difference between code in a constructor and code anywhere else. For instance, you could call So it doesn't mean anything to say that X can only be done in a class constructor, for any X, because you don't have any guarantees that anything specific can't happen. The Dart approach to construction is to have actual guarantees, like, "no final variable can be observed to have two or more distinct values", as opposed to Java), or "during construction of a fresh object, until the end of the phase where the constructor initializer lists have been executed, Hence, as soon as the first constructor body starts executing, we're in normal execution land, and there are no restrictions on what you can do. In particular, it doesn't make sense to say "you can only do X in the body of a constructor". |
Hi,
I have published a package yesterday: https://pub.dev/packages/dispose
It basically exposes a
class
to bindStreamSubscriptions
,Timers
andStreamControllers
. So those listeners are cancelled/closed in thedispose()
function.I was tired of having to bind variables to cancel/close stuff.
For example, instead of
final _ctrl = StreamController()
in aclass
, you have to:I read the page https://dart.dev/null-safety and started playing with NNBD.
As I have ADHD, I just searched for the
late
keyword and couldn't find if could be used along withfinal
.Well, as I try to improve the Dart community, I've created an SO question:
https://stackoverflow.com/questions/64038304/can-late-and-final-be-used-together
Weirdly I was downvoted, but oh well.
Someone linked the explanation regarding
late final
and even thought it is a good thing, I think we can have a benefit for more strictlate
.Hence the
lazy
keyword, it sits betweenlate
andfinal
.Basically a final variable that must be set in the constructor and can not be changed.
What is so different from
late final
?It is checked at compile time and there can not have any setters to it.
The text was updated successfully, but these errors were encountered: