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

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: stack, optional, node, constructor, tree, construction, overflow, initialiser, linked, E08263, recursion, property