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