What mistakes does EK9 reject when a constructor hands 'this' to a child object or callback?

← Classes and OOP · Ref: Q2183

The parent link (Q2181) lets a constructor give one of its own properties a dynamic class or function that captures 'this', while the object is still incomplete. Everything else that would use the half-built object is still rejected - each with E08061, and each for the same reason: a property might be read before it has a value.

1. CALLING A CHILD OR CALLBACK BEFORE THE OBJECT IS COMPLETE

'onOpen()' runs 'folder.summary()', which reads 'index'. Called before 'index' is set, it would read nothing. Until the last capture-assignment the property holding the capture IS the object under another name, so calling it, passing it (even to a pure function) or capturing it again is E08061. Fix: move the call after the last child is set (this example prints 'opened' at the end of the constructor).

2. USING THE OWNER IN THE DYNAMIC CLASS'S PROPERTY INITIALISERS
A dynamic class runs its property initialisers when it is created - DURING the capturing statement, while 'index' itself is still being set - so 'prefix as String: `index of ${folder.name()}`' would call into an incomplete object. Its METHODS run only when called, after construction, so they may use the owner freely. Fix: give the initialiser a value that does not need the owner ('prefix as String: "index of"') and use the owner in a method.

3. AN OPEN OR ABSTRACT TYPE

A subtype's own properties are set only AFTER this constructor returns, so in an open or abstract type the object is never complete inside its own constructor: a child created there could call a method the subtype overrides and read the subtype's unset properties. EK9 is strict here - an open or abstract type never hands out 'this' from a constructor, not even to its own property. Fix: keep the type closed (the default), or create the child later, in a method, once construction is over.

THE OTHER RULES, FOR COMPLETENESS

- A capture of 'this' while another property is still unset (and not set by a later capture): E08061 (Q2181).
- 'this' anywhere in a property initialiser, even 'this.width': E08061 (Q2180).
- 'this' in the arguments of this(...) or super(...): nothing has been constructed yet, E08061.
- Passing the child to another object's method in the constructor publishes the parent, exactly as passing 'this' does: E08250.

WHY BE THIS STRICT

Each of these compiles in Java and fails at run time only on the path that happens to read the missing value - often a rare one. EK9 allows exactly one shape (a capture of 'this' stored in the object's own property, as the last step of phase one) and rejects every other use until the object is complete, so a half-built object can never be observed.

Example

defines module qa.oop.parent.link.mistakes

  defines function

    Opened() as abstract
      <- rtn as String?

  defines trait

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

  defines class

    Folder
      name as String?
      onOpen as Opened?
      index as Entry?

      default private Folder()

      Folder()
        -> text as String
        name: text
        onOpen :=? (folder: this) is Opened as function (rtn: folder.summary())
        index :=? (folder: this) with trait of Entry as class
          prefix as String: "index of"
          override describe() as pure
            <- rtn as String: `${prefix} ${folder.name()}`
          default operator ?
        Stdout().println(`opened: ${onOpen()}`)

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

      summary() as pure
        <- rtn as String: `${name} with ${index.describe()}`

      index()
        <- rtn as Entry: index

      default operator ?

  defines program

    ParentLinkMistakes()
      stdout <- Stdout()
      folder <- Folder("docs")
      stdout.println(folder.index().describe())

Common mistakes

E08061 — 'onOpen' holds the folder, and the folder is not complete until 'index' is set - calling it runs summary(), which reads 'index'. Call a child or callback only after the last capture-assignment.

Incorrect:

Stdout().println(`opened: ${onOpen()}`)
        index :=? (folder: this) with trait of Entry as class
          prefix as String: "index of"
          override describe() as pure
            <- rtn as String: `${prefix} ${folder.name()}`
          default operator ?

Correct:

index :=? (folder: this) with trait of Entry as class
          prefix as String: "index of"
          override describe() as pure
            <- rtn as String: `${prefix} ${folder.name()}`
          default operator ?
        Stdout().println(`opened: ${onOpen()}`)

E08061 — A dynamic class's property initialisers run while it is being created - during the statement that is still setting 'index' - so the captured folder is incomplete there. Use the owner in a method, which runs only when called.

Incorrect:

prefix as String: `index of ${folder.name()}`

Correct:

prefix as String: "index of"

E08061 — A subtype's properties are set only after this constructor returns, so an open or abstract type never hands out 'this' from its constructor - not even to its own property. Keep the type closed, or create the child in a method after construction.

Incorrect:

    Folder as open
      name as String?

Correct:

    Folder
      name as String?
Other ways to ask this
  • Why is calling a callback registered in the constructor E08061?
  • Why can't a dynamic class's property initialiser use the owner it captured?
  • Why can't an open class capture this in its constructor?
  • What counts as leaking this from an EK9 constructor?
  • Where can I use this in a constructor in EK9?

Coming from another language?

Java: listeners registered in a constructor ('button.addListener(this)'), callbacks fired from it, overridable calls and initialisers that use 'this' all compile; 'leaking this' is a well-known source of race conditions and nulls that nothing checks. Kotlin: 'Leaking this in constructor of non-final class' is a warning. Swift: a compile error to use 'self' before phase one ends. C++: virtual calls from a constructor dispatch to the base version, silently. EK9: E08061 for every use of the half-built object, E08250 for publishing it.

Keywords: back reference, leaking this, callback registered in constructor, initialiser, parent reference, dynamic class, abstract, constructor, E08250, E08061, open