Why does EK9 reject manual close of local resources?

← Error Handling and Exceptions · Ref: Q802

EK9 has no 'close' statement: 'close conn' is E01094, rejected as early as 'break' or 'return'. Every close is generated, because a close written by hand is unsafe in two ways. If an exception happens before it, the resource leaks:

  conn <- DatabaseConn("test-db")
  doWork()       //an exception here leaks conn

Research shows ~32% of resource leak bugs are caused by exceptions occurring before manual close() calls. And if the value has another owner - the caller that passed it, the try that made it - a close by hand closes it twice, or under an owner still using it.

CORRECT: TRY-WITH-RESOURCES

A local closeable is declared in the try header, and the try closes it when it ends, normally or by exception:

  try
    -> conn <- DatabaseConn("test-db")
    stdout.println(conn.query("SELECT 1"))

WHERE THE OTHER CLOSES COME FROM

- A closeable value you never name is closed by the statement that made it.
- A type's own closeable properties are closed by its 'operator close' - 'default operator close', or the chain after a close you write for extra work.
- A parameter, a capture or a returned value is closed by its owner, never by you.
The close operator is never called as a method either: 'conn.close()' is E81060.

See Q137 for try-with-resources syntax. See Q803 for abandoned resource detection. See Q1600 for who owns a closeable.

Example

defines module qa.errorhandling.manualclose

  defines class

    DatabaseConn
      connName <- String()

      DatabaseConn()
        -> connectionName as String
        connName: connectionName

      query()
        -> queryText as String
        <- queryResult as String: `${queryText} from ${connName}`

      operator close as pure
        require true

      override operator ? as pure
        <- rtn <- true

  defines function

    safeResourceUsage()
      stdout <- Stdout()

      try
        -> conn <- DatabaseConn("test-db")
        queryResult <- conn.query("SELECT 1")
        stdout.println(queryResult)

Common mistakes

E01094 — 'close' is not a statement in EK9: a close written by hand never runs if an exception comes first, and closes twice a value somebody else owns. Declare the local in the try header and the try closes it. See ek9 -h E01094 for details.

Incorrect:

      conn <- DatabaseConn("test-db")
      queryResult <- conn.query("SELECT 1")
      stdout.println(queryResult)
      close conn

Correct:

      try
        -> conn <- DatabaseConn("test-db")
        queryResult <- conn.query("SELECT 1")
        stdout.println(queryResult)
Other ways to ask this
  • What does E01094 'close' is not a statement mean?
  • Why must I use try-with-resources for local variables?
  • How do I properly close resources in EK9?
  • Can I write 'close conn' when I have finished with a connection?

Coming from another language?

Java: allows manual close() but try-with-resources recommended since Java 7. Python: 'with' statement recommended but manual close allowed. Rust: RAII with Drop trait, automatic cleanup. Go: defer close() pattern, easy to forget. C#: using statement for IDisposable. EK9: there is no close statement (E01094) and close() is never called as a method (E81060) - every close is generated.

Keywords: E01094, with, try, leak, close, resources, resource, local, manual