E81057: a callable whose return type is closeable must return a NEW instance; to lend a held resource, return a non-closeable view type (e.g. StringInput)

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

What went wrong

A method, operator, function or dynamic function whose declared return type is closeable (the type has an 'operator close') returns something that is not a NEW instance.

Returning a closeable 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 the value must belong to nobody else. These are already owned:

  • 'this', or one of its own properties, or a view of one: the object closes them, through its own close and the generated chain.
  • A parameter: the caller that passed it owns it.
  • A captured variable: whatever it was captured from owns it.
  • A local, including a try-with-resources resource: the try closes it when it ends, so the caller would receive a closed object.
  • A call whose result may be held elsewhere ('list.getOrDefault(0, ...)', 'iterator.next()', or a method that returns one of its own properties).

A NEW instance is a constructor call, or a call of a method or function that itself declares a closeable return type (it is held to this same rule).

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, try, while or for used as its value. The error is reported on the callable and names the first value that is not new.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1620 (Can a method return a closeable it already holds, or must it return a new one?)
  • ek9 -q 1720 (Why can't a closeable have an open() method that returns this?)
  • ek9 -q 1740 (How do I let other code use a resource my object owns?)
  • ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)
  • ek9 -q 1900 (How can my code use a closeable resource it does not own?)

How to fix it

Construct the value in the callable, or delegate to a factory: an 'open()' that returns a new 'Res(label)' rather than the Res it already holds (the [EXAMPLE] section below).

A held resource is never handed out - not even wrapped in a "view" type of your own: a type that keeps the resource in a property OWNS it (E81062, E81067) and would close it too. Code that only USES a resource borrows it in one of three ways, and none of them can close it:

  1. As an ARGUMENT - the LOAN pattern. Keep the resource in its owner and give the owner a method that takes a function and calls it with the resource. The callee borrows it for that call only (good_loan.ek9 below). The function type the borrower implements:
LineUser() as abstract
  -> lines as StringInput
  <- rtn as Integer?

 and the owner's method that lends its file input to one:

lend()
  -> user as LineUser
  <- rtn as Integer: user(input)
  1. Through a VIEW the standard library already provides. A TCP connection's input() and output() are a StringInput and a StringOutput, which have no close; hold or pass those, never the connection. A file's CloseableStringInput has no such view, so use the loan for files.
  2. Inside the try that owns it: a dynamic class made there that captures the resource BY NAME borrows it for as long as the try runs. The try still closes it, exactly once.

A method named 'open()' that returns 'this' is this error too: acquire the resource in the CONSTRUCTOR instead, and let try-with-resources (or the statement, when the value is never named) own it from the moment it exists.

Common causes

  • An accessor ("getter") for a closeable property.
  • An operator such as '+' on a closeable type returning 'this'.
  • Returning a parameter, or a try-with-resources resource, from a helper.
  • A fluent 'open()' that returns 'this' (construct, then open).

See also

  • E81057: Closeable return is not a new instance (this error)
  • E81056: Closeable held in a named local
  • E81062: Closeable slot given a value that is not new (who owns what)

Examples

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

Fails with E81057: a callable whose return type is closeable must return a NEW instance; to lend a held resource, return a non-closeable view type (e.g. StringInput)

#!ek9
defines module error.e81057.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}`)

    Holder
      held as Res?

      default private Holder()

      Holder()
        -> label as String
        held: Res(label)

      resource()
        <- rtn as Res: held

      default operator ?

      default operator close

  defines program

    CloseableBorrowedReturn()
      stdout <- Stdout()

      try
        -> holder <- Holder("heldInside")
        stdout.println(holder.resource().read())
        stdout.println("still using holder")
        stdout.println(holder.resource().read())

      stdout.println("done")

Fixed: Return a new instance - the caller owns it

#!ek9
defines module error.e81057.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}`)

    Opener
      label as String: "fresh"

      open()
        <- rtn as Res: Res(label)

      default operator ?

Fixed: Never hand a held resource out: lend it to a function for one call (the loan)

#!ek9
defines module error.e81057.good.loan

  defines function

    LineUser() as abstract
      -> lines as StringInput
      <- rtn as Integer?

    countLines() is LineUser
      -> lines as StringInput
      <- rtn as Integer: 0
      while lines.hasNext()
        line <- lines.next()
        if not line.empty()
          rtn++

  defines class
    Document
      input as CloseableStringInput?

      default private Document()

      Document()
        -> path as FileSystemPath
        input: TextFile(path).input()

      lend()
        -> user as LineUser
        <- rtn as Integer: user(input)

      default operator close
      default operator ?

Questions and answers

These Q&A entries show E81057 and its fix: