Can I put a closeable resource in a List, an Optional or my own generic type?
← Error Handling and Exceptions · Ref: Q1780
No. A closeable type (one with an 'operator close') is never a type argument of a generic type or a generic function. List of TCP, Optional of Res, Dict of (String, HTTP), a Box of Res of your own and Acceptor of TCP are all error E81061, and so are the same parameterisations when they are INFERRED - [server], Optional(res), {"key": res}, or a generic function called with a closeable, which infers T as that type:
keepAll()
-> server as TCP
servers <- [server] // ERROR E81061 - a List of TCP
A generic's 'T constrain by Res', where Res is closeable, is refused at its declaration too: it could only ever be given a closeable.
The reason is ownership. A closeable value has exactly one owner, and the compiler must see that owner close it: a try-with-resources resource, a property closed by its type's generated close, or the caller of a callable that returns a new one. A generic type or function has no such owner, so what it held would never be closed - or would be closed while somebody else still owned it.
A closeable can be a PARAMETER type: that is a loan, and the lender keeps ownership. So pick one of three shapes:
1. Hold the resources as properties of a type you own, which declares 'default operator close' - its generated close closes them.
2. Acquire each one as a try-with-resources resource where it is used.
3. Lend it to the code that needs it, as a parameter, instead of collecting it.
A callback that receives a resource is declared as a non-generic function type with that parameter - the way TCPReadyHandler takes a TCP - never as Acceptor of TCP.
The program below holds two resources as properties of an owning type, lends each one to a function, and keeps a third in a try; every close prints, so you can see each resource closed exactly once.
Example
defines module qa.errorhandling.closeablenevertypeargument defines class Line text as String? default private Line() Line() -> initialText as String text: initialText greeting() as pure <- rtn as String: `hello ${text}` operator close as pure stdout <- Stdout() stdout.println(`closed ${text}`) default operator ? <?- Owns its resources as properties - not a List of Line - and its generated close closes them, last first -?> Lines first as Line? second as Line? default private Lines() Lines() -> firstText as String secondText as String first: Line(firstText) second: Line(secondText) greetAll() show(first) show(second) default operator close default operator ? defines function <?- A closeable as a PARAMETER is a loan: the lender keeps ownership and closes it -?> show() -> line as Line stdout <- Stdout() stdout.println(line.greeting()) defines program CloseableTypeArgumentDemo() try -> lines <- Lines("one", "two") lines.greetAll() try -> line <- Line("three") show(line)
Common mistakes
E81061 — A closeable type is never a type argument: [line] would be a List of Line, and a List has no owner that closes what it holds. Lend the resource as a parameter instead, or hold it as a property of a type you own. See ek9 -h E81061 for details.
Incorrect:
try -> line <- Line("three") lines <- [line] show(line)
Correct:
try -> line <- Line("three") show(line)
Other ways to ask this
- Why is 'servers <- [TCP(properties)]' an error?
- Can I keep several open connections in a List and close them later?
- Why can't I write Optional of TCP or Dict of (String, HTTP)?
- Can a generic type parameter be constrained by a closeable type?
- What does E81061 mean?
Coming from another language?
Java: a List<Socket> or Optional<Connection> compiles, and closing every element is left to a finally loop that is easy to get wrong or forget. C#: a List<IDisposable> is the same; 'using' only covers a single variable. Rust: a Vec<File> drops each element when the Vec is dropped, because ownership moves into the container. EK9: a container never owns a resource; ownership stays with a try, a property of a type you write, or the caller of a factory, and the compiler refuses the type argument (E81061).
Keywords: List, try-with-resources, type argument, operator close, generic, ownership, Dict, E81061, constrain by, Optional, closeable, close, loan