Can a method return a closeable it already holds, or must it return a new one?

← Error Handling and Exceptions · Ref: Q1620

Returning a closeable value hands it to the caller, and the caller closes it - as a try-with-resources resource, or at the end of the statement when it is never named. So a method, operator, function or dynamic function whose declared return type is closeable must return a NEW instance (E81057 otherwise). That is a constructor call, or a call of another callable that itself declares a closeable return type, because that callable is held to the same rule:

  openLog()
    <- rtn as CloseableStringInput: TextFile("log.txt").input()

Anything somebody else already owns is an error: 'this', one of its own properties or a view of one (the object's generated close closes those), a parameter (the caller owns it), a captured variable, a try-with-resources resource (the try closes it as it ends, so the caller would get a closed object), or a call whose result may be held elsewhere. Every way the returning variable gets a value is checked - its initialiser, each assignment (an 'if rtn := ... then rtn?' head included) and each arm of a switch used as its value:

  passBack()
    -> input as CloseableStringInput
    <- rtn as CloseableStringInput: input   // ERROR E81057

A resource you hold is never handed out - not by returning it, and not wrapped in a 'view' type of your own, because a type that keeps the resource in a property owns it and would close it too (E81062, E81067). To let other code USE it, LEND it: the LOAN pattern (QA1740) keeps the resource in its owner and gives the owner a method that takes a function and calls it with the resource, so the callee borrows it for that call only. The other two borrower forms - a view the standard library provides, such as a TCP connection's input() and output(), and a dynamic class inside the owning try that captures the resource by name - are in Q1900. Never acquire with a fluent open() that returns 'this' (QA1720). The program below has a factory, a factory that delegates to it, and an owner that hands out a fresh closeable while keeping its own.

Example

defines module qa.errorhandling.closeablereturns

  defines class

    Tracker
      name as String?

      default private Tracker()

      Tracker()
        -> label as String
        name: label

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

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

      default operator ?

    Owner
      tracker as Tracker?

      default private Owner()

      Owner()
        -> label as String
        tracker: Tracker(label)

      spare()
        <- rtn as Tracker: Tracker("spare")

      describe() as pure
        <- rtn as String: tracker.describe()

      default operator close

      default operator ?

  defines function

    makeTracker()
      -> label as String
      <- rtn as Tracker: Tracker(label)

    delegatingFactory()
      -> label as String
      <- rtn as Tracker: makeTracker(label)

  defines program

    CloseableReturnDemo()
      stdout <- Stdout()

      try
        -> made <- delegatingFactory("made")
        stdout.println(made.describe())

      try
        -> owner <- Owner("owned")
        stdout.println(owner.describe())
        try
          -> spare <- owner.spare()
          stdout.println(spare.describe())

Common mistakes

E81057 — spare() declares a closeable return type, so it must return a NEW instance; returning the property 'tracker' would let the caller close the object Owner still owns and closes itself. See ek9 -h E81057 for details.

Incorrect:

      spare()
        <- rtn as Tracker: tracker

Correct:

      spare()
        <- rtn as Tracker: Tracker("spare")
Other ways to ask this
  • Why is a getter for my connection property error E81057?
  • How do I lend a resource I own without the caller closing it?
  • Can a function return its parameter when the type has operator close?
  • Why can't my operator + on a closeable class return this?

Coming from another language?

Java: a getter may return a field that is an AutoCloseable, and nothing says who closes it - the caller's try-with-resources then closes the object's own connection under it. C#: the same with IDisposable; CA2000/CA2213 are advisory. Rust: returning a borrowed &T cannot be dropped by the caller, and returning an owned value moves it out. EK9: a declared closeable return must be a new instance (E81057); lend a held resource as an argument instead (the loan pattern).

Keywords: factory, lend, return, E81057, ownership, close, getter, loan, operator close, closeable