Hi Peter,
we've been thinking more about this... as it was noted in this thread, perhaps the biggest value of 'lone' instance initializers is that they contain _common_ initialization code that is replicated across _all_ constructors. Do we need this for records? Probably no, we don't - the reason being that a record is a well-behaved construct that acts as a transparent wrapper for its state, with an equally well-defined way to construct it (the canonical constructor).

The way I see it, is that it is more a bug than a feature the fact that you can have 5 unrelated constructors on a record class - because one of the values behind opting for a record (vs. a class) in the first place, is to have control over the way in which record instances are created - which is in fact backed up by serialization as well (serialization guarantees that creation will always go through the canonical constructor).

So I think having a constructor that doesn't delegate to the canonical constructor is potentially dangerous, as it exposes an alternate way to construct a record which is not the _blessed_ one, might skip some of the invariant checks etc.

Let's say we demand that all secondary constructors starts off by delegating to the canonical constructor:

this(...)

If that's the case, then all initialization goes through the canonical constructor, which also means that the canonical constructor is also the code where you'd put the statements you otherwise would have put into an instance initializer.

In other words, we want alternate constructors to be used as a way of providing convenience overloads, as in this case:

record Point(int x, int y) {

    Point() { this(0, 0); }

}

I don't think there's anything to be gain by making secondary constructors more flexible as, by doing so, we would also lose an important invariant of record construction - that all record creation flows through the canonical constructor.

Therefore, I'd like to propose that:

* a secondary, non-canonical constructor must always start with this(...) (the target of the delegation might be another secondary constructor, but you will eventually reach the canonical one)
* instance initializers on records are banned

Maurizio



On 06/10/2019 17:51, Peter Levart wrote:
On Sunday, October 6, 2019 6:21:45 PM CEST Brian Goetz wrote:
In these scheme, canonical constructor also acts as instance initializer,
since it is always called from other constructors. Classical instance
initializer is therefore not needed any more and could be prohibited in
record types.
I would agree that instance initializers in records are mostly useless,
and keeping them around adds some complexity.  Any work that can be done
in an II could also be done in a compact ctor with about the same number
of keystrokes:

      { ++instanceCount; }

vs

     Foo { ++instanceCount; }

The argument for keeping them is to minimize the number of gratuitous
differences between records and classes.  But, "it is a compile-time
error for a record class to have an instance initializer" is a pretty
simple spec change... and probably no one will notice.
I know that making special rules in record constructors (about being able to
access instance fields) is also increasing the number of gratuitous
differences between records and classes, but in order to ban instance
initializers, there has to be a an alternative way to specify initializing
code that is always executed. Perhaps it is enough just to suggest users to
put such code into canonical constructor and always call that constructor from
other constructors as opposed to forcing them to do that with language
constraints.

Regards, Peter



Reply via email to