Which of my types must declare their own operator close, and what may that close do?
← Error Handling and Exceptions · Ref: Q1860
If you hold it, you close it. Three rules decide where a close lives and what it may do.
1. A class, record, component or dynamic class with a closeable PROPERTY must DECLARE its own 'operator close' (E81067). An inherited close does not count: it was written before the sub-type added its property and cannot see it, so the property would never be closed. A sub-type that adds a closeable property declares its own close - usually 'default operator close'. Closing is generated: after the close's body (none, for the default) it closes the type's own closeable properties, last acquired first, and then calls its super class's close:
Journal is Ledger notes as Page? ... default operator close
A sub-type that adds no closeable property relies on the inherited close. Injected properties ('x as T!') are closed by the application that owns them, and a 'by'-delegation proxy close closes its delegate.
2. A trait's 'operator close' is ABSTRACT - it has no body (E81066). A trait holds no properties, so it has nothing of its own to close; the class with the trait writes the close.
3. The 'operator close' of an OPEN or ABSTRACT type can never call a method a subtype may override, nor hand out 'this' (E81068). A subtype's generated close closes the subtype's own properties before its super's close runs, so an overridden method called from the super's close would work on resources that are already closed. Call a PRIVATE helper, or the methods of the properties the type itself holds. This is the same rule E08061 applies to the constructor of an open type, at the other end of the object's life.
The program below has an open Ledger that holds one Page, tidies up in its own close and lets the generated close close the Page, a Journal that adds a second Page and declares its own close, and a Bookmark whose trait declares an abstract close. Every close prints, so you can see each Page closed exactly once.
Example
defines module qa.errorhandling.declareyourownclose defines trait <?- A trait's operator close has no body: it is abstract -?> Markable operator close as pure defines class Page label as String? default private Page() Page() -> initialLabel as String label: initialLabel write() stdout <- Stdout() stdout.println(`writing in ${label}`) operator close as pure stdout <- Stdout() stdout.println(`closed ${label}`) default operator ? <?- Open: its close calls only a private helper and the methods of the property it holds -?> Ledger as open title as String? page as Page? default private Ledger() Ledger() -> initialTitle as String title: initialTitle page: Page(initialTitle) describe() as pure <- rtn as String: `ledger ${title}` write() page.write() private tidy() as pure stdout <- Stdout() stdout.println(`tidy ${title}`) operator close as pure tidy() default operator ? <?- Adds a closeable property, so it declares its own close and chains to the super's -?> Journal is Ledger notes as Page? Journal() -> title as String super(title) notes: Page(`${title} notes`) default operator close default operator ? <?- The class with the trait writes the close -?> Bookmark with trait of Markable page as Page? Bookmark() page: Page("bookmark") mark() page.write() default operator close default operator ? defines program DeclareYourOwnClose() try -> journal <- Journal("journal") journal.write() try -> bookmark <- Bookmark() bookmark.mark()
Common mistakes
E81067 — Journal adds a closeable property, so it must declare its own operator close: the inherited Ledger close cannot see 'notes', which would never be closed. 'default operator close' closes what the type holds and then calls the super's close. See ek9 -h E81067 for details.
Incorrect:
Journal()
-> title as String
super(title)
notes: Page(`${title} notes`)
Correct:
Journal()
-> title as String
super(title)
notes: Page(`${title} notes`)
default operator close
E81066 — A trait's operator close is abstract: a trait holds no properties, so it has nothing to close. The class with the trait writes the close. See ek9 -h E81066 for details.
Incorrect:
Markable
operator close as pure
stdout <- Stdout()
stdout.println("trait close")
Correct:
Markable
operator close as pure
E81068 — Ledger is open, and describe() is a method a subtype may override. A subtype's close closes its own properties before Ledger's close runs, so the override would see closed resources. Call a private helper such as tidy() instead. See ek9 -h E81068 for details.
Incorrect:
operator close as pure describe()
Correct:
operator close as pure tidy()
Other ways to ask this
- Why must my sub-class declare operator close when its super already has one?
- Why can't a trait's operator close have a body?
- Why can't the operator close of an open class call one of its own methods?
- What do E81066, E81067 and E81068 mean?
Coming from another language?
Java: a subclass that adds a field and forgets to override close() (or forgets super.close()) leaks silently; a close() that calls an overridable method is a known trap that no compiler reports. C#: analyzers CA2213 and CA2215 look for the forgotten member and base call, and CA2215 ships off by default. C++: a destructor body runs with virtual calls bound to its own class, so a base destructor cannot reach a derived member. EK9: a type with a closeable property must declare its own close (E81067), a trait's close is abstract (E81066), and an open type's close cannot dispatch to an override (E81068).
Keywords: override, close, E81068, open, operator close, super.close, trait, closeable, E81066, E81067, property, abstract