Can I keep a closeable in a variable of a trait or Any type?
← Error Handling and Exceptions · Ref: Q1640
No. Closeability cannot be erased by a store. A closeable value (its type has an 'operator close') may only be STORED - declared, assigned, or kept in a property or the returning variable - where the DECLARED type is closeable too. Storing one where the declared type has no close - a trait such as IO or StringInput, or Any - is error E81059:
input as StringInput: TextFile("data.txt").input() // ERROR E81059 stdout.println(input.next())
Through a StringInput nobody can call close, the statement that made the value sees it stored and does not close it, and the named-local rule (E81056) does not apply because StringInput is not closeable - so it would leak.
Keep the closeable type, and name it as a try-with-resources resource:
try -> input <- TextFile("data.txt").input() stdout.println(input.next())
An ARGUMENT is not a store. Passing a closeable to a parameter of a narrower, non-closeable type lends the callee a view; the owner - the statement for an unnamed value, the try for a named one - still closes it:
show(TextFile("data.txt").input())
A slot declared as the Closeable trait keeps its close, so it is allowed. A standard stream (Stdin, Stdout, Stderr) is borrowed, never closed, so 'input as StringInput: Stdin()' is fine.
The program below lends a closeable to a function that only knows its trait, then keeps one in a try; its close prints, so you can see each one closed.
Example
defines module qa.errorhandling.closeableerasure defines trait Greeter greeting() as pure <- rtn as String? defines class Line with trait of Greeter text as String? default private Line() Line() -> initialText as String text: initialText override greeting() as pure <- rtn as String: `hello ${text}` operator close as pure stdout <- Stdout() stdout.println(`closed ${text}`) default operator ? defines function show() -> greeter as Greeter stdout <- Stdout() stdout.println(greeter.greeting()) defines program CloseableErasureDemo() stdout <- Stdout() show(Line("argument")) try -> line <- Line("kept") stdout.println(line.greeting())
Common mistakes
E81059 — A closeable value may only be stored where the declared type is closeable: through a Greeter nothing could ever close it. Keep the closeable type in a try-with-resources resource. See ek9 -h E81059 for details.
Incorrect:
greeter as Greeter: Line("kept") stdout.println(greeter.greeting())
Correct:
try -> line <- Line("kept") stdout.println(line.greeting())
Other ways to ask this
- Why is 'input as StringInput: TextFile(path).input()' an error?
- Can I store an HTTP server in a variable declared as IO?
- Can I pass a closeable to a parameter whose type is not closeable?
- Why must a variable holding a closeable keep the closeable type?
- What does E81059 mean?
Coming from another language?
Java: an AutoCloseable can be assigned to any supertype (Object, Readable, an interface without close) and then never closed; nothing in the language notices. C#: an IDisposable stored as object or an interface without Dispose is the same leak. Rust: ownership travels with the value whatever trait object it is viewed through, so Drop still runs. EK9: the declared type of the place a closeable is stored must keep its close (E81059); a narrower view is only for an argument, while the owner keeps it.
Keywords: StringInput, IO, leak, try-with-resources, operator close, argument, trait, CloseableStringInput, E81059, closeable, store, declared type, close