How can an object hold a reference to itself, like a doubly linked node that starts as a ring of one?

← Classes and OOP · Ref: Q1471

A property of the object's own type may refer back to the object: assign 'this' to it in the constructor, with ':', ':=', '=' or ':=?' ('prev: this', 'this.owner :=? this'). Storing the object in one of its OWN properties is not a use of it - nothing is read through it and nothing outside the object holds it - so it sets that property like any other assignment, even while other properties are still unset. A doubly linked node can start as a ring of one before its label is set:

  Node
    prev as Node?
    next as Node?
    label as String?
    default private Node()
    Node()
      -> text as String
      prev: this
      next: this
      label: text
    label()
      <- rtn as String: label
    override operator ? as pure
      <- rtn as Boolean: label?

THE PROPERTY IS THE OBJECT UNDER ANOTHER NAME

Only 'this' itself may be stored that way, and in that constructor the property then IS the object. Calling a method through it ('itself.doubled()'), copying it to a local, or reading 'this.itself' is a use of the object, so it needs every property set first, exactly as a method call on 'this' does (E08061; see Q1396). Passing it to another object's method is publishing a half-built object (E08250), as passing 'this' is. Once every property is set it can be used freely. The same store one step removed - a child created with a capture of 'this' and kept in the object's own property ('section :=? (owner: this) with trait of Part as class ...') - is the parent link: see Q2181.

GENERATED OPERATORS ARE SAFE ON A CYCLE

A type like this can reach itself through its properties, and the compiler works that out. Its GENERATED operators ('default operator ?', '$', '$$', '==', '#?') then carry a cycle guard, so a property that refers back never recurses without end: '?' treats the object it is already checking as set; '$' and '$$' on a cycle are unset (never JSON with a hole where the loop was); '==' assumes a pair it is already comparing is equal, so two identical rings are equal; and '#?' leaves out the properties that lead back. A type that cannot reach itself pays nothing. The node above still writes its own '?', because only its label says whether it is set.

Example

#!ek9
defines module qa.oop.property.refers.to.itself

  defines class

    Node
      prev as Node?
      next as Node?
      label as String?

      default private Node()

      Node()
        -> text as String
        prev: this
        next: this
        label: text

      label()
        <- rtn as String: label

      next()
        <- rtn as Node: next

      prev()
        <- rtn as Node: prev

      linkAfter()
        -> node as Node
        follower <- next
        node.joinBetween(this, follower)
        follower.joinPrev(node)
        next: node

      joinBetween()
        ->
          before as Node
          after as Node
        prev: before
        next: after

      joinPrev()
        -> node as Node
        prev: node

      override operator ? as pure
        <- rtn as Boolean: label?

    Tally
      itself as Tally?
      count as Integer?
      total as Integer: 0

      default private Tally()

      Tally()
        -> start as Integer
        itself: this
        count: start
        total: itself.doubled()

      doubled()
        <- rtn as Integer: count * 2

      value()
        <- rtn as Integer: total

      override operator ? as pure
        <- rtn as Boolean: total?

  defines program

    SelfReferenceDemo()
      stdout <- Stdout()

      first <- Node("first")
      stdout.println(`after first: ${first.next().label()}`)
      stdout.println(`before first: ${first.prev().label()}`)

      first.linkAfter(Node("second"))
      second <- first.next()
      stdout.println(`ring: ${first.label()}, ${second.label()}, ${second.next().label()}`)
      stdout.println(`back: ${first.prev().label()}`)

      stdout.println($Tally(21).value())

Common mistakes

E08061 — 'itself' was set to 'this', so calling doubled() through it is a call on the object - and doubled() reads 'count', which is not set yet. Storing 'this' is fine at any point; reading through it waits until every property is set.

Incorrect:

total: itself.doubled()
        count: start

Correct:

count: start
        total: itself.doubled()
Other ways to ask this
  • Can a constructor assign 'this' to one of the object's own properties?
  • How do I write 'me: this' in an EK9 constructor without E08061?
  • How do I build a circular linked list node whose next and previous point to itself?
  • Why is reading through a property set to 'this' in the constructor still E08061?

Coming from another language?

Java: 'this.next = this;' in a constructor compiles, and so does every use of a half-built object - the compiler checks neither. Swift: two-phase initialisation - 'self' cannot be used until every stored property is set, so a property that must point at 'self' is declared optional or implicitly unwrapped and set after. Kotlin: 'lateinit var next: Node' and 'next = this' in init, with a 'Leaking this' warning on any other use. EK9: storing 'this' in an own property is allowed at any point in the constructor; any read through that property waits for the object to be complete (E08061).

Keywords: E08061, E08250, circular, constructor, doubly linked, linked list, node, property, ring, self reference, self-referential