E81072: a closeable made but never named is kept by what it is given to, so the statement cannot close it and nothing else would - if the callee only uses it, name it as a try resource ('try -> res <- Res()') and pass 'res'; if it keeps it, keep it inside the try, pass a value taken from it, or make the holder a closeable that owns it
What went wrong
A closeable value is made, and never named, as an ARGUMENT of a call that may hand it back - for example 'anything <- handBack(Res("kept"))', where 'Res' has an 'operator close' and 'handBack' returns what it was given as an 'Any'.
An unnamed closeable is owned by the statement that made it and closed when that statement finishes. Here the call may return the closeable itself (or a view of it) under a type that can hold it - Any, a trait, an open or abstract type, a generic 'T', or a type holding one - so the result may still be in use after the statement ends. Closing it would leave a closed value in use; leaving it open would leak it, because nothing is ever left to close it.
The same holds for the right operand of an operator that may return it, for the RECEIVER of a call - 'Res().me()' where 'me' is a method of an open super that returns 'this' - and for an argument of a CONSTRUCTOR that keeps it in a property that cannot close it (an Any or trait-typed property, a List, an Optional or a dynamic function holding it - directly, through a local, a capture or a stream, through this(...)/super(...) or a call that retains it): the object would then hold a resource its statement had closed, and not closing it would leak it. A constructor that keeps it in a closeable property is a transfer (its holder closes it), and one that only uses it while it runs takes a loan, closed by the statement - neither is an error.
The same is true of a call that KEEPS the closeable beyond the call: a method, operator or function that adds it to a collection another argument holds, stores it in a property of the object it was called on (directly, through a local, in a collection, an Optional or a dynamic function), captures it in a dynamic function that outlives the call, or hands it to another call that keeps it. What a callable written in EK9 keeps is read from its body (every use of the parameter, and of each local made to refer to it); what a built-in keeps is what it declares - 'InputSanitizer(reporter)' keeps its reporter, so 'InputSanitizer(TextFile(path).output())' is this error: the file would never be closed and its buffered report would never reach the disk. A callable that only uses the argument while it runs - reads it, gives it to a pure function, stores only a String it read from it - takes a loan, closed by the statement, and is not affected.
The RECEIVER is read the same way: a method that stores 'this' - adds it to a collection another argument holds ('items += this'), hands it to a callee that keeps it, or passes it to another of its own methods that does - keeps the object it was called on, so 'Tracked("r").registerIn(kept)' is this error: the statement would close 'Tracked' and 'kept' would then hold a closed object. A method that only reads its own properties, calls its own methods or passes 'this' to a pure function does not keep it.
A compound assignment hands its operand to the target's operator, and is judged by what the target's TYPE could hold, not by what that operator does: 'registry += Res("x")' or 'bases :~: Res("x")' is this error when the target could keep its operand (a list of a type that can hold it, or a type of your own with a property that could hold it, or one that is open). A target that cannot hold it at all (a built-in value, or a type of your own whose properties are only Strings and numbers) takes a loan, closed by the statement.
A holder whose type cannot hold the closeable at all keeps nothing: a stream that maps each element to its tag and fills a 'List of String' is not this error, where one filling a 'List of Any', a list of a type holding a property of the closeable's type, or a list of its super type is.
This also covers a call through a trait's or an abstract method, a method an open class lets a sub-type override, or a function delegate (a variable holding a function or a dynamic function): the implementation that runs is not known, so every implementation in the program is considered, and one no program code implements may return or keep any argument.
A call whose return type cannot hold a view (String, Integer, a record of values) is not affected: the statement closes the loan as it ends.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)
How to fix it
If the call only USES the closeable, or hands it back to be used where it is called, name it as a try-with-resources resource and pass the name ('try -> res <- Res("kept")', then 'handBack(res)'): the try owns it and closes it when the try ends (good.ek9 in the [EXAMPLE] section below).
If the call KEEPS it - adds it to a collection, stores it in a property, captures it - naming it does not help: the try closes it at the end and the keeper then holds a closed value (E81075). Keep the resource inside the try and let the keeper hold what the work produced; or pass a VALUE taken from the closeable (its text or number) instead of the closeable; or make the holder a closeable that owns the resource - a class with the resource as a property, closed by its own 'operator close', constructed with a NEW resource (Contract 14).
Or change what the call returns: a value that cannot hold the closeable (the text or number you needed from it) lets the statement close it as before.
Common causes
- Passing a freshly made resource to a helper typed to take and return 'Any' or a trait.
- Calling a trait or open-class method, or a function delegate, whose implementation returns its argument.
- Calling a method that returns 'this' (a fluent method on an open super) on a freshly made resource.
- Giving a freshly made resource to a constructor that stores it in an Any property.
- Giving a freshly made resource to a method or function that adds it to a list, stores it in a property or captures it, when the call is the only place it was made.
- Calling a method that registers 'this' somewhere ('items += this') on a freshly made resource.
- Handing a freshly made output to a built-in that keeps it, such as 'InputSanitizer(TextFile(path).output())'.
See also
- E81072: An unnamed closeable passed to a call that may hand it back (this error)
- E81075: A try resource outlives its try (the named form of a keep)
- E81056: Closeable held in a named local (must be a try-with-resources resource)
- E81059: Closeability cannot be erased by a store
- E81071: An unnamed closeable in the arguments of super(...)/this(...)
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E81072: a closeable made but never named is kept by what it is given to, so the statement cannot close it and nothing else would - if the callee only uses it, name it as a try resource ('try -> res <- Res()') and pass 'res'; if it keeps it, keep it inside the try, pass a value taken from it, or make the holder a closeable that owns it
#!ek9 defines module error.e81072.bad defines class Res name as String? default private Res() Res() -> label as String name: label default operator ? operator close as pure stdout <- Stdout() stdout.println(`closed ${name}`) defines function handBack() -> held as Any <- rtn as Any: held useIt() <- rtn as Boolean: true anything <- handBack(Res("kept")) rtn: anything?
Fixed: Name the closeable as a try resource and pass it, when the call only uses it or hands it back
#!ek9 defines module error.e81072.good defines class Res name as String? default private Res() Res() -> label as String name: label default operator ? operator close as pure stdout <- Stdout() stdout.println(`closed ${name}`) defines function handBack() -> held as Any <- rtn as Any: held useIt() <- rtn as Boolean: false try -> res <- Res("kept") anything <- handBack(res) rtn: anything?
Questions and answers
These Q&A entries show E81072 and its fix: