Constructors: building an object in two phases

An object is built in two phases: first every property gets a value, then the object is complete and may be used. Constructors, this(...), super(...) and private constructors are shaped to keep the phases apart.

Why

Building an object has two phases. In the first, every property is given a value: the super class constructor runs, then this class's property initialisers in the order they are declared, then the constructor body. In the second, the object is complete, and only then may anything use it. EK9 keeps the phases apart, so a half-built object can never be seen.

Everything about constructors follows from that. A constructor hands its work to another with 'this(...)' or 'super(...)' only as its first act, so nothing runs against an object whose super class has not been built. A constructor is judged through its 'this(...)' chain, so a private constructor that leaves a property unset can only be reached through 'this(...)' from one that sets it, never by a construction call (E08264). A constructor with no 'super(...)' relies on the super class's no-argument constructor, so when that one is private it must call one it can reach (E07176), and a sub class with no constructor at all needs a super constructor it can call (E07177).

It is also why calling a method, or handing out 'this', before every property is set is E08061. In other languages the same mistake reads a field that has not been given its value yet, and quietly gets its default (null or zero). In EK9 it does not compile.

A child that genuinely needs a reference back to the parent that creates it has one way to be given it while the parent is still being built, the parent link. (Once the parent is complete it may hand itself out like any other object.) The parent link has its own questions and answers, linked under Related, rather than being repeated here.

The correct form

#!ek9
defines module site.classes.constructors

  defines constant
    noReading <- 0

  defines class

    Reading as open
      //A property initialiser runs before ANY constructor body, so 'label' is "reading" there.
      label as String: "reading"
      amount as Integer?

      //A no-argument constructor that delegates with this(...) rather than repeating the work.
      Reading()
        this(noReading)

      Reading()
        -> amount as Integer
        this.amount: amount
        label += `: ${amount}`

      //A private constructor is for the class's own use; it delegates first, so every property is set.
      private Reading()
        ->
          label as String
          amount as Integer
        this(amount)
        this.label: `${label}: ${amount}`

      relabelled()
        -> newLabel as String
        <- rtn as Reading: Reading(newLabel, amount)

      describe() as pure
        <- rtn as String: label

      default operator ?
      default operator $

    Sample extends Reading
      units as String?

      //Its own property has no initial value, so its default constructor must be private.
      default private Sample()

      //super(...) runs the Reading constructor (and Reading's initialisers) before this body.
      Sample()
        ->
          amount as Integer
          units as String
        super(amount)
        this.units: units

      override describe() as pure
        <- rtn as String: `${super.describe()} ${units}`

      default operator ?
      default operator $

  defines program

    Constructors()
      stdout <- Stdout()

      stdout.println(Reading().describe())
      stdout.println(Reading(21).describe())
      stdout.println(Reading(21).relabelled("indoor").describe())
      stdout.println(Sample(7, "mm").describe())

Output

reading: 0
reading: 21
indoor: 21
reading: 7 mm

Wrong forms

Each of these is written by people and by models, and each fails to compile - the error is what the compiler tells you.

Relying on a private super constructor

A constructor with no super(...) call relies on the super class's no-argument constructor; when that is private, call an accessible super(...) constructor instead.

Fails with E07176: a constructor cannot rely on the implicit super() - the super has no accessible no-argument constructor (it is 'private', or there is none) - call or provide an accessible initialising super constructor

#!ek9
defines module bad.site.classes.constructors.privatesuper

  defines class

    Reading as open
      amount as Integer?

      default private Reading()

      Reading()
        -> amount as Integer
        this.amount: amount

      default operator ?

    Sample extends Reading
      units as String?

      Sample()
        -> units as String
        this.units: units

      default operator ?

A sub class with no constructor when the super class constructor is private

With no constructor of its own, Sample would be given a no-argument one that calls the super class's no-argument constructor, and that one is private. Write a constructor for the sub class that calls a super constructor it can reach.

Fails with E07177: the super type has no accessible no-argument constructor (it is 'private', or there is none), so a default constructor cannot be generated here - you must define a constructor for this sub-type that calls an accessible super(...)

#!ek9
defines module error.e07177.bad

  defines class

    Reading as open
      amount as Integer?

      default private Reading()

      Reading()
        -> initial as Integer
        this.amount: initial

      default operator ?

    //No constructor, and none can be generated: it would have to call the private Reading()
    Sample extends Reading
      describe()
        <- rtn as String: "a sample"

Calling a method before every property is set

A constructor that calls a method of its own object before every property has a value is using a half-built object. In Gauge, twice() reads 'reading' before it is set, and 'doubled', the property being assigned, has no value yet either, so setting 'reading' first does not help. Every property, the one being assigned included, needs a value before the method is called: give 'doubled' an initial value ('doubled as Integer: 0') and set 'reading' before calling twice(), as the fix on the E08061 page does. In Document, the section captures 'this' before 'title' is set. Set 'title' first; then handing out 'this' to the section is the parent link.

Fails with E08061: the object is used before it is fully constructed - set every property before calling its methods

#!ek9
defines module error.e08061.bad

  defines trait

    Part
      describe() as pure
        <- rtn as String?

  defines class

    Gauge
      reading as Integer?
      doubled as Integer?

      Gauge()
        doubled: twice()
        reading: 21

      twice()
        <- rtn as Integer: reading * 2

      default operator ?

    Document
      title as String?
      section as Part?

      default private Document()

      Document()
        -> text as String
        section :=? (owner: this) with trait of Part as class
          override describe() as pure
            <- rtn as String: `a section of ${owner.name()}`
          default operator ?
        title: text

      name() as pure
        <- rtn as String: title

      default operator ?

Constructing through a private constructor that leaves a property unset

A constructor is judged through its this() chain: a private one that leaves a property unset can only be reached through this(...) from a constructor that sets it, never by a construction call.

Fails with E08264: this constructor leaves properties without a value - it can only be used through this() from a constructor that sets them; construct through a constructor that sets every property

#!ek9
defines module bad.site.classes.constructors.privatepartial

  defines class

    Reading
      label as String?
      amount as Integer?

      default private Reading()

      Reading()
        ->
          label as String
          amount as Integer
        this.label: label
        this.amount: amount

      private Reading()
        -> label as String
        this.label: label

      relabelled()
        -> newLabel as String
        <- rtn as Reading: Reading(newLabel)

      default operator ?