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