How do I release the resources my class holds - and what does 'default operator close' close for me?
← Error Handling and Exceptions · Ref: Q1880
You declare a close, and EK9 generates the closing. 'default operator close' closes every closeable property your type holds - each one only if it is present, LAST acquired FIRST - and then calls your super class's close. A close you write yourself is for extra work only: its body runs first, with every property still open, and the same generated closing follows it as its finally, so it runs even when the body throws. You never close a property by hand - there is no 'close' statement (E01094) - and never call 'super.close()' (E81060): both would close something twice.
Notebook as open cover as Page: Page("cover") index as Page: Page("index") default operator close // closes index, then cover
Every close runs even when one fails: the first failure is rethrown after all of them, and later ones are attached to it. A constructor that fails part way closes the properties it had acquired before it rethrows.
Two rules keep closing honest. 'default operator close' with nothing to close - no closeable property and no super class close - is E81070: it would only make the type closeable and force its callers into a 'try'. And a captured NEW closeable belongs to whatever captured it: a dynamic CLASS can own it (it declares 'default operator close', which closes it), but a dynamic FUNCTION has no close, so capturing a new closeable there is E81069. A capture of something that is not new - a try resource captured by name - is a borrow, and its owner closes it.
In the program below, Diary's written close runs first with its page open, then 'today' is closed, then Notebook's close closes 'index' and 'cover'; a dynamic class owns and closes the page it captured; and a dynamic function borrows a try resource the try closes.
Example
defines module qa.errorhandling.releasewhatyouhold defines trait Readable text() <- rtn as String? defines function reading() as abstract <- rtn as String? defines class Page label as String? default private Page() Page() -> initialLabel as String label: initialLabel read() as pure <- rtn as String: `reading ${label}` operator close as pure stdout <- Stdout() stdout.println(`closed ${label}`) default operator ? <?- Holds no closeable: it needs no close at all -?> Tag name as String? default private Tag() Tag() -> initialName as String name: initialName describe() as pure <- rtn as String: `tag ${name}` default operator ? <?- Holds two pages and asks for the generated close: last acquired, first closed -?> Notebook as open cover as Page: Page("cover") index as Page: Page("index") default operator close default operator ? <?- A written close does extra work first; its own page and Notebook's close follow it -?> Diary is Notebook today as Page: Page("today") override operator close as pure stdout <- Stdout() stdout.println(`diary still open: ${today.read()}`) default operator ? defines program ReleaseWhatYouHold() stdout <- Stdout() try -> diary <- Diary() stdout.println("writing the diary") try -> reader <- (page: Page("captured")) with trait of Readable as class override text() <- rtn as String: page.read() default operator close default operator ? stdout.println(reader.text()) try -> loose <- Page("loose") fn <- (loose) is reading as function rtn: loose.read() stdout.println(fn()) stdout.println(Tag("tagged").describe())
Common mistakes
E01094 — 'close' is not a statement: the generated close already closes 'today' after the body, so closing it by hand would close it twice. A written close does extra work only. See ek9 -h E01094 for details.
Incorrect:
override operator close as pure stdout <- Stdout() stdout.println(`diary still open: ${today.read()}`) close today
Correct:
override operator close as pure stdout <- Stdout() stdout.println(`diary still open: ${today.read()}`)
E81070 — Tag holds no closeable property and has no super class close, so a 'default operator close' would close nothing - it would only make Tag closeable and force its callers into a try. See ek9 -h E81070 for details.
Incorrect:
describe() as pure <- rtn as String: `tag ${name}` default operator close default operator ?
Correct:
describe() as pure <- rtn as String: `tag ${name}` default operator ?
E81069 — A dynamic function has no close, so it can never close a NEW closeable it captures. Capture a try resource by name (a borrow), or capture the new value in a dynamic class that declares 'default operator close'. See ek9 -h E81069 for details.
Incorrect:
fn <- (fresh: Page("fresh")) is reading as function rtn: fresh.read()
Correct:
fn <- (loose) is reading as function rtn: loose.read()
Other ways to ask this
- Do I write 'close' for each closeable property in my operator close?
- In what order does EK9 close the properties of my class?
- Does my sub-class have to call super.close()?
- Can a dynamic class or a dynamic function own a resource it captures?
- What do E81069 and E81070 mean?
Coming from another language?
Java: close() is written by hand - each field closed in turn, super.close() remembered, and a field added later forgotten; one throwing close skips the rest unless every call is wrapped. C++ and Swift: destructors release members (in reverse order) and the base automatically - EK9's generated close is the same model, with 'default operator close' to ask for it. C#: Dispose is hand-written; analysers CA2213/CA2215 warn about forgotten fields and base calls. EK9: the compiler writes the closing, so neither can be forgotten.
Keywords: close, operator close, E01094, capture, default operator close, E81060, dynamic class, closeable, super, E81070, property, E81069