Skip to content
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

Clarify use of this in initializing instance variables #5310

Merged
merged 17 commits into from
Jan 3, 2024
Merged
4 changes: 2 additions & 2 deletions src/language/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ Non-final instance variables and
an implicit *setter* method. For details,
check out [Getters and setters][].

If you initialize a non-`late` instance variable where it's declared,
If you initialize a non-`late` `final` instance variable where it's declared,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why you'd think that this doesn't initialize i:

class A {
  int i = 1;
}

void main() {
  print(A().i); // '1'.
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Respected,

I am talking about non-late but final instance variable which is initialized at declaration time. In that case "this" keyword will not be accessible.

class Point{
final double a;
final double b;

 //initialized at declaration
final double c=2.7;

Point(this.a,this.b,this.c);

}

In this case "this.c" will show error. Because it is already initialized.

But if "c" is not final then you can reassign it a new value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am talking about non-late but final instance variable

OK, but the paragraph you're referring to is about access to this in an initializing expression (which is the expression e that may occur after = in a variable declaration), and it is true for final as well as non-final variables that the initializing expression doesn't have access to this. (Also, the given explanation is valid for both kinds of variables.)

It's a bit like seeing "You can't go faster than 25MPH here!" and suggesting that we should change it to "You can't go faster than 25MPH here on rainy days!". If the first sentence is true then the second one is true, too, but it isn't helpful to introduce a reduction of scope when the statement is true for the original, broader scope. It indirectly implies that the original statement wasn't true, but that's incorrect: It was true.

Same structure here: The fact that there is no access to this in an initializing expression is true for all non-late instance variables, not just for final ones.

[Edit: added the word 'non-late' near the end.]

Copy link
Contributor

@MaryaBelanger MaryaBelanger Nov 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, @pdhankhar61 your issue is with the existing statement:

non-late instance variable initializers can’t access this.

And you're saying that statement is incorrect because you tried accessing this for the non-late instance variables a, b and c, and it works:

class Point{
double a;
double b;

 //initialized at declaration
double c=2.7;

Point(this.a,this.b,this.c); // no errors, accessing `this` DOES work even though c is already initialized
}

But you found that statement is true if the variable is final, so you're updating the statement to specify final?

class Point{
final double a;
final double b;

 //initialized at declaration
final double c=2.7;

Point(this.a,this.b,this.c); // error - 'c' is final and was given a value when it was declared, so it can't be set to a new value.
}

I could be totally missing the point of the section, but I'm finding it confusing too. It also doesn't seem like the example code starting on line 183 is related to the paragraph immediately preceding it (the same paragraph in question), which makes it even more confusing.

@eernstg (or anyone) So it's not just about initialization of non-late instance variables in general, but specifically that the paragraph makes it sound like you can't use this to initialize non-late instance variables -- why does it say that doesn't work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is what it means that the initializing expression of a non-late instance variable does not have access to this:

class Point {
  double a = this.b; // (1)
  double b;
  Point(this.b); // (2)
}

At (1), we have the initializing expression this.b. We could have written a plain b, because that's just a concise notation for the same thing. In both cases it is a compile-time error to have such an initializing expression, because "that location does not have access to this".

The point is that the initializing expression e of a non-late instance variable is evaluated before the object under construction is complete, so we can't allow any user-written code (like e) to see the object, yet.

If the variable is late then it can also have an initializing expression, and in this case it does have access to this, because it's only evaluated later, when the object is complete. So late double a = this.b; is fine.

However, the occurrence of this at (2) is completely different: That's a declaration of a formal parameter of the constructor named Point, and this particular kind of parameter is known as an 'initializing formal'.

The syntax of an initializing formal is roughly <type>? 'this' '.' <identifier>, and it works as follows: The type <type> is usually omitted (the ? is there to indicate that we can omit it), so let's ignore that. It's an error if this declaration is a formal parameter of anything other than a non-redirecting generative constructor. The identifier must be the name of an instance variable of the enclosing class / mixin class / enum. Finally, at run time it causes the given instance variable to be initialized to the actual argument which is passed for that parameter.

So this.b at (2) is not an expression, and it doesn't allow us to write any code that accesses the object at a point in time where it is incomplete, it's just a "magic word" which indicates that this formal parameter is an initializing formal. The constructor could also have been written as Point(double b) : this.b = b;, so the magic word this is just a request for this particular kind of syntactic sugar. The syntax this.b = b is in turn equally magic: The occurrence of this.b is not a general expression, it's a special syntax that allows us to specify that this particular element in the initializer list is used to initialize the instance variable named b. (We could omit this again). The point is again that initialization of the state of the object must take place under very controlled circumstances, because we cannot let user-written code access the object before it is complete. That's what all those "not a general expression" incantations are about.

In particular, 'access to this' has nothing to do with initializing formals, in spite of the fact that the latter uses this as part of the syntax.

But it might be helpful to say, somewhere, that an initializing formal parameter isn't an "access to this" because it isn't an expression.

Copy link
Contributor Author

@pdhankhar61 pdhankhar61 Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay Now, I have understood you point @eernstg sir. Like that statement was confusing without example. I think you should add the example for that statement.

Delay in response is because of github, because it sends notifications with a delay.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @pdhankhar61!

So what have we learned here? Taking a look at the section as a whole, the following comes to mind:

I think it's confusing that this section says 'All uninitialized instance variables have the value null', and it does not even mention that for many instance variables (namely the ones whose declared type is potentially non-nullable) it is simply a compile-time error if they are left uninitialized.

If we mention this then it also makes sense to mention the error, perhaps right after "can't access this":

double initialX = 1.5;

class Point {
  double? x = initialX; // OK, can access declarations that do not depend on `this`.
  double? y = this.x; // Compile-time error, cannot access `this`.
}

@MaryaBelanger, I understand that this kind of documentation should be extremely succinct and to the point, but what do you think about adding something like this?

We should probably also mention somewhere that an initializing formal isn't an expression and hence it isn't an "access to this" even in the case where it's written literally as this.x, but that might be better placed in a section where initializing formals are introduced (the same section does use one initializing formal, but only refers to it as 'a constructor parameter', so perhaps initializing formals aren't described here at all).

the value is set when the instance is created,
which is before the constructor and its initializer list execute.
As a result, non-`late` instance variable initializers can't access `this`.
As a result, non-`late` `final` instance variable initializers can't access `this`.

<?code-excerpt "misc/lib/language_tour/classes/point_with_main.dart (class+main)" replace="/(double .*?;).*/$1/g" plaster="none"?>
```dart
Expand Down