Why does a type that constructs itself in a property initialiser or constructor fail to compile?
← Classes and OOP · Ref: Q1403
If constructing a type ALWAYS constructs the same type again, construction can never complete - at run time it is a stack overflow - so the compiler rejects it (E08263) and shows the cycle. It follows only what always runs when an object is built: the super constructor, the property initialisers (they run for EVERY instance, before the constructor body), a this(...) delegation and its arguments, and the calls in a constructor body, function or closed method that are not inside a condition, loop, catch, short-circuit or ':=?'. The cycle can be direct ('next as Node: Node()'), between two types whose initialisers build each other, through a function, a this(...) delegation cycle, or a generic: 'Node extends Base of Node' where Base does 'me as T: T()' builds a Node while building a Node.
FIX
Hold a link in an 'Optional of Node', which starts empty, and fill it when there is something to link - and give the type its own '?' if an empty link should not make it unset (a defaulted '?' needs every property set). Hold children in a 'List of Node', which also starts empty. Build the recursive part only under a condition that ends it - recursion under a condition is legal, because the compiler does not need to prove it ends. A plain 'next as Node?' does not help on its own: every property must be initialised before use (E08060).
See Q1396 for using an object before it is constructed. See Q1397 for the order property initialisers run in.
Example
#!ek9 defines module qa.classes.constructionrecursion defines class <?- A linked node: the link starts empty, so building a Node never builds another. A Node is set once it has a label - an empty link must not make it unset. -?> Node label as String: String() next as Optional of Node: Optional() of Node Node() -> text as String label: text link() -> node as Node next: Optional(node) describe() <- rtn as String: label if next? rtn: `${label} -> ${next.get().describe()}` override operator ? as pure <- rtn as Boolean: label? <?- A tree built to a depth: a child is built only while depth is above zero. -?> Tree children as List of Tree: List() of Tree Tree() -> depth as Integer if depth > 0 children += Tree(depth - 1) size() <- rtn as Integer: 1 for child in children rtn += child.size() default operator ? defines program ConstructionDemo() stdout <- Stdout() first <- Node("first") first.link(Node("second")) stdout.println(first.describe()) treeSize <- Tree(3).size() stdout.println($treeSize)
Common mistakes
E08263 — Without the condition every Tree builds another Tree, so construction can never complete. See ek9 -h E08263 for details.
Incorrect:
children += Tree(depth - 1)
Correct:
if depth > 0 children += Tree(depth - 1)
Other ways to ask this
- What is E08263 construction recursion?
- Why is 'next as Node: Node()' an error?
- How do I write a linked node or a tree in EK9 without infinite recursion?
- Why does Node extends Base of Node fail when Base constructs T?
Coming from another language?
Java, C#, Kotlin, Swift, Rust and Go all compile 'class Node { Node next = new Node(); }' (or its equivalent) and overflow the stack at run time; Java, C# and Kotlin reject only a this(...) delegation cycle. EK9 rejects every construction that always constructs its own type again, including through functions, closed methods and generics, as a compile error.
Keywords: E08263, construction, constructor, initialiser, linked, node, optional, overflow, property, recursion, stack, tree