Who closes a closeable value, and can I close one myself?
← Error Handling and Exceptions · Ref: Q1600
No - you never close anything yourself. Every closeable value has exactly ONE owner, decided by where it lives, and the owner's close is generated by the compiler:
- a LOCAL is a try-with-resources resource, and the try closes it when it ends;
- a value that is never NAMED - a call result, a constructed value - is closed by the statement that made it;
- a PROPERTY is owned by its object: the type's close - 'default operator close', or an 'operator close' you write for extra work - closes it after its body, last acquired first, then calls its super class's close;
- a PARAMETER belongs to the caller, which lends it for the call;
- a CAPTURE of a named value belongs to the scope that made the dynamic function or class;
- the RETURNING variable is handed to the caller, who becomes its owner;
- 'this' is owned by whoever made the object.
So there is no 'close' statement (E01094) and the close operator is never called as a method (E81060). A close written by a non-owner is a double close, or a close while the owner is still using the value - Java allows both, EK9 cannot express them. A type owns its properties simply by declaring its close:
default operator close
A slot that will close its value - a closeable property or a try resource - therefore takes only a NEW value (E81062).
In the program below, Session owns its Connection and its generated close closes it; report() only uses the connection it is given and leaves the close to the try that owns it.
Example
#!ek9 defines module qa.errorhandling.onlytheownercloses defines class Connection name as String? default private Connection() Connection() -> label as String name: label describe() as pure <- rtn as String: `connection ${name}` operator close as pure stdout <- Stdout() stdout.println(`closed ${name}`) default operator ? Session connection as Connection? default private Session() Session() -> label as String connection: Connection(label) describe() as pure <- rtn as String: connection.describe() default operator close default operator ? defines function report() -> connection as Connection stdout <- Stdout() stdout.println(connection.describe()) defines program CloseOwnershipDemo() stdout <- Stdout() try -> session <- Session("owned") stdout.println(session.describe()) try -> connection <- Connection("borrowed") report(connection)
Common mistakes
E01094 — 'close' is not a statement: a parameter belongs to the caller, whose try closes it, so a close here would close it twice. Every close is generated by the owner. See ek9 -h E01094 for details.
Incorrect:
stdout.println(connection.describe())
close connection
Correct:
stdout.println(connection.describe())
E81060 — The close operator is never called as a method: the caller's try closes the connection it lent, so this call would close it twice. See ek9 -h E81060 for details.
Incorrect:
stdout.println(connection.describe())
connection.close()
Correct:
stdout.println(connection.describe())
Other ways to ask this
- Can a function close a resource it was passed as a parameter?
- Why is 'close conn' error E01094?
- Can I write 'close conn' for a connection held in a property?
- Who owns a closeable value in EK9?
- Can a dynamic function close a variable it captured?
Coming from another language?
Java: any code holding an AutoCloseable may call close() on it - a method that was only passed a stream often closes it, and the caller's try-with-resources closes it again; a class may close a field from any method. Rust: a borrower (&T) cannot drop what it borrowed; only the owner drops it. C#: CA2000/CA2213 are warnings. EK9: only the owner closes, and every close is generated - there is no close statement (E01094) and close() is never called as a method (E81060).
Keywords: E01094, E81060, capture, close, double close, operator close, owner, ownership, parameter, property