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