Can I keep a closeable in a variable of a trait or Any type?

← Error Handling and Exceptions · Ref: Q1640

No. Closeability cannot be erased by a store. A closeable value (its type has an 'operator close') may only be STORED - declared, assigned, or kept in a property or the returning variable - where the DECLARED type is closeable too. Storing one where the declared type has no close - a trait such as IO or StringInput, or Any - is error E81059:

  input as StringInput: TextFile("data.txt").input()   // ERROR E81059
  stdout.println(input.next())

Through a StringInput nobody can call close, the statement that made the value sees it stored and does not close it, and the named-local rule (E81056) does not apply because StringInput is not closeable - so it would leak.

Keep the closeable type, and name it as a try-with-resources resource:

  try
    -> input <- TextFile("data.txt").input()
    stdout.println(input.next())

An ARGUMENT is not a store. Passing a closeable to a parameter of a narrower, non-closeable type lends the callee a view; the owner - the statement for an unnamed value, the try for a named one - still closes it:

  show(TextFile("data.txt").input())

A slot declared as the Closeable trait keeps its close, so it is allowed. A standard stream (Stdin, Stdout, Stderr) is borrowed, never closed, so 'input as StringInput: Stdin()' is fine.

The program below lends a closeable to a function that only knows its trait, then keeps one in a try; its close prints, so you can see each one closed.

Example

defines module qa.errorhandling.closeableerasure

  defines trait

    Greeter
      greeting() as pure
        <- rtn as String?

  defines class

    Line with trait of Greeter
      text as String?

      default private Line()

      Line()
        -> initialText as String
        text: initialText

      override greeting() as pure
        <- rtn as String: `hello ${text}`

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

      default operator ?

  defines function

    show()
      -> greeter as Greeter
      stdout <- Stdout()
      stdout.println(greeter.greeting())

  defines program

    CloseableErasureDemo()
      stdout <- Stdout()

      show(Line("argument"))

      try
        -> line <- Line("kept")
        stdout.println(line.greeting())

Common mistakes

E81059 — A closeable value may only be stored where the declared type is closeable: through a Greeter nothing could ever close it. Keep the closeable type in a try-with-resources resource. See ek9 -h E81059 for details.

Incorrect:

      greeter as Greeter: Line("kept")
      stdout.println(greeter.greeting())

Correct:

      try
        -> line <- Line("kept")
        stdout.println(line.greeting())
Other ways to ask this
  • Why is 'input as StringInput: TextFile(path).input()' an error?
  • Can I store an HTTP server in a variable declared as IO?
  • Can I pass a closeable to a parameter whose type is not closeable?
  • Why must a variable holding a closeable keep the closeable type?
  • What does E81059 mean?

Coming from another language?

Java: an AutoCloseable can be assigned to any supertype (Object, Readable, an interface without close) and then never closed; nothing in the language notices. C#: an IDisposable stored as object or an interface without Dispose is the same leak. Rust: ownership travels with the value whatever trait object it is viewed through, so Drop still runs. EK9: the declared type of the place a closeable is stored must keep its close (E81059); a narrower view is only for an argument, while the owner keeps it.

Keywords: StringInput, IO, leak, try-with-resources, operator close, argument, trait, CloseableStringInput, E81059, closeable, store, declared type, close