E08250: 'this' cannot be passed as an argument to, or stored into, another object inside a constructor body

Category: E08xxx Pre-IR checks (flow analysis) · Reported in phase: FULL_RESOLUTION · Classification: THIS_ESCAPES_CONSTRUCTOR · All error codes

What went wrong

Inside a constructor body, 'this' must not be passed as an argument to any call, nor STORED into another object. This prevents publication-during-construction races: if 'this' escapes to another thread (or to any object that retains the reference) before the constructor has finished initialising fields, another thread - or the other object calling back - may observe partially-initialised state.

Storing is the same escape as passing, reached without a call:

p.parent: this            a property of ANOTHER instance
p.kids += this            a collection another object holds
reg.members() += this     a collection another object returns
reg += this               another object's mutating operator
others += this            a collection passed in as a parameter

Storing 'this' in the object's OWN property is a self-reference and is allowed: 'me: this', 'kids += this', 'this.kids += this'. A chain through an own property reaches another object and is an escape: 'this.me.parent: this'.

A local that refers to 'this' (other <- this, other := this, or a further local copied from one) is 'this' under another name, and passing or storing it is the same escape. So, in that constructor, is an own property it stored 'this' into: after 'me: this', 'reg.register(me)' is the same escape as 'reg.register(this)'.

So is a dynamic function or dynamic class that captures 'this' (or such a local): '(me: this) is Lazy as function ...' holds the half-built object, so passing or storing it - directly, through a local, or captured again by another dynamic function - is the same escape. Calling it inside the constructor is not. The same holds for an own property given such a capture (the parent link, 'child :=? (owner: this) with trait of Child as class ...'): after it, 'reg.adopt(child)' in that constructor publishes the parent.

This is a structural rule — the language eliminates the entire class of safe-publication bugs that Java's memory model requires careful discipline to avoid.

Rationale

Constructors run in a single thread and complete before the object is "visible" to the rest of the program. Passing 'this' to a thread- spawning receiver (e.g., TCP.accept, Signals.register, Ek9Async), to ANY method that retains the reference, or storing it where another object can reach it, breaks this guarantee. Once the reference leaks, another thread (or the other object) can read fields the constructor has not yet written.

EK9 forbids the leak at its source — the call argument or the store. No memory model required, no defensive copying required, no synchronisation required.

How to fix it

  1. Construct first, then link or register AFTER construction completes, from a separate statement - make the child, then 'parent.adopt(child)'.
  2. Use a factory function that constructs the object and registers it AFTER the constructor returns (the [EXAMPLE] section below: the constructor no longer registers 'this'; 'publishAll' makes the Publisher, then registers it).
  3. Pass the constructor arguments needed to compute behaviour, not 'this' itself. Many cases that look like 'this'-escape are really "register me by ID" — pass the ID.
  4. The parent link: the constructor may HOLD its child (the child captures 'this'), but it must not PUBLISH it. Publish it after construction, from whoever created the parent - for example a factory that constructs the parent, then registers the child the parent exposes ('registry.adopt(node.listener())'). This holds even once every property is set: the rule is about publishing during construction, not about which fields are set.

Common causes

  • Self-registration patterns: 'registry.register(this)' or 'registry += this' in a constructor that should run AFTER construction completes.
  • Parent/child wiring done in the child's constructor ('parent.kids += this') instead of 'parent.adopt(child)' after it.
  • Listener/observer wiring done too early.
  • Capturing 'this' into a closure created in the constructor body and handing that closure to another object.

See also

  • E08061: 'this' used before every property is set
  • E08251: shared state mutation outside lock (the main data-race rule)
  • E08256: MutexLock construction outside field initialiser ek9 -q 2181 (How does a child object hold a reference back to its parent when the parent creates it in its constructor?) ek9 -q 2183 (What mistakes does EK9 reject when a constructor hands 'this' to a child object or callback?)

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E08250: 'this' cannot be passed as an argument to, or stored into, another object inside a constructor body

#!ek9
defines module error.e08250.bad

  defines class

    Registry
      items as List of Publisher: List() of Publisher

      default Registry()

      register()
        -> p as Publisher
        items += p

      default operator ?

    Publisher
      name as String?

      Publisher()
        ->
          n as String
          reg as Registry

        this.name: n

        reg.register(this)

      default operator ?

Fixed: Construct the object first, then hand it on

#!ek9
defines module error.e08250.good

  defines class

    Registry
      items as List of Publisher: List() of Publisher

      default Registry()

      count()
        <- rtn as Integer: length items

      register()
        -> publisher as Publisher
        items += publisher

      default operator ?

    Publisher
      name as String?

      default private Publisher()

      Publisher()
        -> label as String
        name: label

      default operator ?

  defines function

    publishAll()
      -> registry as Registry
      <- rtn as Integer?
      publisher <- Publisher("news")
      registry.register(publisher)
      rtn: registry.count()

Fixed: Or give this to a pure Consumer, which cannot keep it

#!ek9
defines module error.e08250.good.consumer

  defines record

    ConfigurationDetails
      host as String: "localhost"
      port as Integer: 8080

      default ConfigurationDetails() as pure

      ConfigurationDetails() as pure
        -> initializer as Consumer of ConfigurationDetails
        initializer(this)

      default operator ?

Questions and answers

These Q&A entries show E08250 and its fix: