E81067: if you hold it, you close it - a type with a closeable property must declare its own 'operator close' ('default operator close' or one you write); an inherited close cannot see this type's property

Category: E81xxx-E83xxx Test configuration, execution and coverage · Reported in phase: PRE_IR_CHECKS · Classification: CLOSEABLE_HOLDER_WITHOUT_OWN_CLOSE · All error codes

What went wrong

If you hold it, you close it. A class, record, component or dynamic class has a closeable property (its type has an 'operator close') but declares no 'operator close' of its own. Nothing would ever close that property.

The type must DECLARE its own close - 'default operator close', or one you write. An inherited close does not count: it was written for the super-type and cannot see a property a sub-type adds, so a sub-type that relies on it leaks its own property. An abstract close does not count either, and a 'by'-delegation proxy close closes only its delegate.

A dynamic class that captures a NEW closeable ('(res: Res("x"))') owns it and holds it the same way. Not counted as held: an injected property ('store as Store!' - the application owns it), the delegate of a 'by' proxy close, a property typed as a bare type parameter T, and a capture of a value that is not new (a named variable or '(owner: this)' - a borrow).

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1860 (Which of my types must declare their own operator close, and what may that close do?)
  • ek9 -q 1880 (How do I release the resources my class holds - and what does 'default operator close' close for me?)
  • ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)
  • ek9 -q 1780 (Can I put a closeable resource in a List, an Optional or my own generic type?)
  • ek9 -q 1900 (How can my code use a closeable resource it does not own?)

How to fix it

Declare the close in the type that holds the property. Its generated close closes the property and then calls the super class's close (the [EXAMPLE] section below: a Journal that adds 'notes' to a Ledger declares its own 'default operator close').

Write an 'operator close' instead only for extra work; it runs first, with everything still open, and the generated close follows it.

If the value is not this type's to close, do not hold it as a property: take it as a parameter where it is used (a loan), or acquire it in a try.

Common causes

  • A sub-type adds a connection or file to a super-type that already has a close, and relies on the inherited one.
  • A holder that keeps a resource it was given, expecting someone else to close it.
  • A record or component that makes a resource in a property initialiser.
  • A 'by'-delegated type that also holds a second closeable property.

See also

  • E81067: A type with a closeable property must declare its own operator close (this error)
  • E81066: A trait's operator close must be abstract
  • E81068: An open type's operator close calls an overridable method
  • E81056: Closeable held in a named local (must be a try resource)

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E81067: if you hold it, you close it - a type with a closeable property must declare its own 'operator close' ('default operator close' or one you write); an inherited close cannot see this type's property

#!ek9
defines module error.e81067.bad

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

      read() as pure
        <- rtn as String: `read ${name}`

      default operator ?

      operator close as pure
        stdout <- Stdout()
        stdout.println(`closed ${name}`)

    Ledger as open
      book as Res?

      default private Ledger()

      Ledger()
        -> title as String
        book: Res(title)

      default operator ?

      default operator close

    Journal is Ledger
      notes as Res?

      Journal()
        -> title as String
        super(title)
        notes: Res(`${title} notes`)

      default operator ?

Fixed: Declare its own close - the generated close closes notes, then calls Ledger's

#!ek9
defines module error.e81067.good

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

      read() as pure
        <- rtn as String: `read ${name}`

      default operator ?

      operator close as pure
        stdout <- Stdout()
        stdout.println(`closed ${name}`)

    Ledger as open
      book as Res?

      default private Ledger()

      Ledger()
        -> title as String
        book: Res(title)

      default operator ?

      default operator close

    Journal is Ledger
      notes as Res?

      Journal()
        -> title as String
        super(title)
        notes: Res(`${title} notes`)

      default operator ?

      default operator close

Questions and answers

These Q&A entries show E81067 and its fix: