Let me summarize what we're proposing for record constructors.  I think we can simplify it so that it is easy to do parameter validation and normalization without falling off the syntactic cliff, and with fewer new features.  (As a bonus, I think these ideas mostly generalize to more concise non-record constructors too, but I'm going to leave that for another day -- but I'll just note that one of our goals for records is for them to be "just macros" for a set of finer-grained features, so that even if a class doesn't meet the requirements to be a record, it can still benefit from more concise construction, or equals/hashCode, or whatever.)

Goals
-----

it should be easy to add constructor parameter validation, like

    Preconditions.require(x > 0);

without significant repetition of record elements.  Similarly, it should be easy to normalize arguments before they are committed to fields:

    if (name == null)
        name = "";

again without significant repetition.  If there are ancillary fields, it should be easy to initialize them in the constructor body without bringing back any boilerplate.

Ideally, these mechanisms are consistent with construction idioms for non-record classes (records and classes should not be semantically different).

Record Constructors
-------------------

A record class:

    record Point(int x, int y) { }

has a "class signature" `(int x, int y)`.  Record classes always have a _default constructor_ whose signature matches the class signature; these can be implicit or explicit.

The constructor syntax

    record Point(int x, int y) {
        Point {
        }
    }

is proposed as a shorthand for an explicit default constructor:

    record Point(int x, int y) {
        Point(int x, int y) {
        }
    }

The arguments A0..An are divided into super-arguments A0..Ak and this-arguments Ak+1..An.  The default super invocation is super(A0..Ak).

If a default constructor does not contain an explicit super-call, an implicit super constructor call is provided at the start of the constructor, just as we do now with implicit no-arg super-constructors.

An implicit field initialization, consisting of `this.xi = xi` for i in k+1..n, is added to the _end_ of the constructor.

This makes both validation and normalization work; if the constructor body contains only:

    Preconditions.require(x > 0);

(this is just a library call), the code is executed after the super-call, and `x` refers to the arguments.  Just like in constructors today.  Similarly, normalization:

    if (name == null)
        name = "";

is executed after the super-call, but before the field initialization, so any the update to the parameter "sticks", and `name` refers to the argument, not the field, just as today.  So existing idioms work, just by removing the boilerplate.

We can protect against double-initialization either by (a) not allowing the user to explicitly initialize fields corresponding to Ak+1..An, or (b) if the corresponding field is definitely initialized by the explicit constructor body, leave it out of the implicit initialization code.

If users want to adjust what gets passed to the super constructor, just use an explicit super-call.  If users want to adjust what gets written to the field, overwrite the parameter before it is written to the field.  Both of these are consistent with how constructors work today.

If the record has extra fields (if allowed), the constructor can just initialize them:

    Point {
        norm = Math.sqrt(x*x + y*y);
    }

Again, `x` and `y` here refer to constructor arguments, and everything works, with no repetition.

Summary:
 - The required idioms work, just leaving out the boilerplate
 - Very similar to existing constructors
 - No need to support statements before super (though we can add this later, if we see fit)
 - No need for `default.this(...)` idiom *at all*

(pause for agreement)

Reply via email to