When exactly is a closeable I never name closed - what if it is made inside an 'and', a ternary or a coalescing operator?

← Error Handling and Exceptions · Ref: Q1940

An unnamed closeable is closed as soon as the evaluation that made it finishes - EK9 releases resources as soon as it can. Usually that evaluation is the whole statement, but an operand that runs only SOMETIMES is an evaluation of its own: the right of 'and' and 'or', a ternary arm, the right of '??', '?:', '<?' and '>?', the right of ':=?', a later 'else if' condition, a condition after a guard and a 'case' value. A closeable made there is closed when that operand finishes, and only if it ran:

  both <- enabled and Tracker("checked").ready()   // closed before 'both' is set, only when enabled

A loop condition is evaluated again on every iteration, so it closes what it made every time:

  while count < limit and Tracker(`loop ${count}`).ready()

When the closeable IS the conditional's result - a ternary arm, or either side of a coalescing operator - it cannot be closed when the arm finishes, because the value is still to be used. The evaluation that USES the result owns it instead, and closes it when it finishes, only if that arm actually made it:

  stdout.println(`chosen ${enabled <- Tracker("first") else Tracker("second")}`)   // closes the one that was made

A named try resource picked by the same conditional is never closed by it - its try closes it.

Example

defines module qa.errorhandling.closedassoonasfinished

  defines class

    Tracker
      name as String?

      default private Tracker()

      Tracker()
        -> label as String
        name: label

      ready() as pure
        <- rtn as Boolean: true

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

      operator $ as pure
        <- rtn as String: `tracker ${name}`

      default operator ?

  defines function

    report()
      -> enabled as Boolean
      stdout <- Stdout()

      both <- enabled and Tracker("checked").ready()
      stdout.println(`both ${both}`)

      stdout.println(`chosen ${enabled <- Tracker("first") else Tracker("second")}`)

      count <- 0
      limit <- 2
      while count < limit and Tracker(`loop ${count}`).ready()
        stdout.println(`loop body ${count}`)
        count++

  defines program

    ClosedAsSoonAsFinished()
      report(true)
      report(false)
Other ways to ask this
  • Is a closeable made on the right of 'and' closed if the left side is false?
  • When is a closeable made in a ternary arm closed?
  • Does a while condition close the closeable it makes on every iteration?
  • Is an unnamed closeable closed at the end of the statement or sooner?

Coming from another language?

Java: a temporary AutoCloseable in an expression is never closed unless it is a try-with-resources resource, and a resource made in only one branch needs a nullable variable and a manual finally. C#: the same, with 'using' declarations at block scope. C++: a temporary is destroyed at the end of the full-expression that made it, whichever branch ran. EK9 closes it earlier still - when the evaluation that made it finishes - and only the ones that were made.

Keywords: condition, coalescing, resource management, temporary, while, close, ternary, short-circuit, closeable