E81063: a closeable slot is assigned once - a property in its own constructor or initialiser, a returning variable once on each path (declared unset), a try resource only by its try; re-pointing it drops the resource it held

Category: E81xxx-E83xxx Test configuration, execution and coverage · Reported in phase: PRE_IR_CHECKS · Classification: CLOSEABLE_SLOT_REPOINTED · All error codes

What went wrong

A closeable SLOT is assigned where it may already hold a resource. Every closeable has one owner and is closed once, so each slot that owns one is assigned once:

  • A closeable PROPERTY is set while its object is constructed: in its own initialiser or its own type's constructor, once on any path. Assigning it outside the constructor, through another object ('other.conn'), or in the constructor after it already holds one (its initialiser ran, or an earlier statement on some path set it) is this error.
  • A closeable RETURNING variable (of a function, method, operator, dynamic function, or a switch/try/while/for expression) that is initialised at its declaration is never assigned again. One declared unset is assigned at most once on any path: not twice in a row, not in one arm of an 'if' and again after it, not in a 'try' body (other than as its last statement) and again in its 'catch', and not in a loop body, which can run again.
  • A try-with-resources RESOURCE is never assigned again: its try closes only what it holds at the end.

Re-pointing drops the resource the slot held, and nothing ever closes it. The guarded form ':=?' does not help: a closeable that failed or was closed is present but UNSET, so ':=?' replaces it - and still drops it.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1841 (How do I reconnect or replace a resource my object holds?)
  • ek9 -q 1720 (Why can't a closeable have an open() method that returns this - how do I acquire a resource?)

How to fix it

For a property, acquire a fresh resource in a try each time one is needed, rather than replacing one held in a property - a 'reconnect()' that assigns a new connection to the property becomes a loop of tries, each with its own resource ('callTwice' in the [EXAMPLE] section below).

For a returning variable, declare it unset and assign it once in each arm, rather than giving it a default resource and replacing it in an arm ('choose' in the same [EXAMPLE]).

For a try resource, start a new try for the new resource.

Or, when an object needs a different resource for its whole life, make a new object - its constructor acquires the resource and its 'operator close' releases it.

Common causes

  • A reconnect(), reset() or failOver() method that assigns a new connection.
  • 'conn :=? Connection(..)' to "open it if it is not open".
  • A property with an initialiser that the constructor assigns again.
  • Assigning another object's closeable property.
  • A returning variable given a default resource and then replaced in an arm.
  • A returning variable assigned in a loop, keeping the last attempt.
  • Replacing a try resource part way through its try.

See also

  • E81063: Closeable slot re-pointed (this error)
  • E81062: Closeable slot given a value that is not new
  • E81057: Callable with a closeable return type returns something not new
  • E81064: Closeable properties acquired out of declaration order

Examples

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

Fails with E81063: a closeable slot is assigned once - a property in its own constructor or initialiser, a returning variable once on each path (declared unset), a try resource only by its try; re-pointing it drops the resource it held

#!ek9
defines module error.e81063.bad

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

      read() as pure
        <- rtn as String: `read ${name}`

      default operator ?

      operator close as pure
        stdout <- Stdout()
        stdout.println(`closed ${name}`)

    Client
      conn as Res?

      Client()
        conn: Res("first")

      reconnect()
        conn := Res("second")

      default operator ?

      default operator close

  defines function

    choose()
      -> choice as Boolean
      <- rtn as Res: Res("default")
      if choice
        rtn: Res("chosen")

Fixed: Use a fresh resource per try; give a returning variable one value on each path

#!ek9
defines module error.e81063.good

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

      read() as pure
        <- rtn as String: `read ${name}`

      default operator ?

      operator close as pure
        stdout <- Stdout()
        stdout.println(`closed ${name}`)

  defines function

    callTwice()
      <- rtn as String: String()
      for attempt in 1 ... 2
        try
          -> conn <- Res(`attempt ${attempt}`)
          rtn: conn.read()

    choose()
      -> choice as Boolean
      <- rtn as Res?
      if choice
        rtn: Res("chosen")
      else
        rtn: Res("default")

Questions and answers

These Q&A entries show E81063 and its fix: