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)
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:
- 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)
- 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.
- 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
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: