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

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: property, doubly linked, circular, node, self reference, linked list, constructor, E08250, E08061, self-referential, ring