What happens when a closeable resource is never closed?

← Error Handling and Exceptions · Ref: Q803

EK9 raises E81056 when a local variable holds a closeable value (its type has an 'operator close') outside a try-with-resources declaration. Nothing would ever close it, so it is a guaranteed resource leak.

THE RULE

A closeable value you never NAME is owned by the statement that created it and is closed when that statement finishes. A closeable value you NAME must be declared as a try-with-resources resource, and is closed when the try ends.

NOT A LOCAL

- RETURNED: the returning variable hands it to the caller (a factory)
- A PROPERTY: the object owns it
- A PARAMETER: the caller owns it
Passing a named local on to a function or constructor does NOT transfer it - the local still holds it.

FIX OPTIONS

1. try-with-resources: closed when the try ends (recommended)
2. Do not name it: FileConn("log.txt").readAll() is closed when its statement finishes
3. Return it: transfer ownership to the caller

Research shows ~30% of resource leak bugs are caused by developers forgetting to close resources entirely.

See Q1520 for the whole ownership rule. See Q802 for manual close rejection. See Q137 for try-with-resources syntax.

Example

defines module qa.errorhandling.resourceneverclosed

  defines class

    FileConn
      fileName <- String()

      FileConn()
        -> name as String
        fileName: name

      readAll()
        <- content as String: "contents of " + fileName

      operator close as pure
        require true

      override operator ? as pure
        <- rtn <- true

  defines function

    safeFileRead()
      stdout <- Stdout()

      try
        -> fileConn <- FileConn("log.txt")
        content <- fileConn.readAll()
        stdout.println(content)

Common mistakes

E81056 — A closeable value held in a named local variable must be a try-with-resources resource, or nothing would ever close it. Declare it in the try. See ek9 -h E81056 for details.

Incorrect:

      fileConn <- FileConn("log.txt")
      content <- fileConn.readAll()
      stdout.println(content)

Correct:

      try
        -> fileConn <- FileConn("log.txt")
        content <- fileConn.readAll()
        stdout.println(content)
Other ways to ask this
  • What triggers E81056 closeable held in a named local?
  • How does EK9 detect abandoned resources?
  • Why must a named closeable be a try-with-resources resource?

Coming from another language?

Java: unclosed resources only detected by FindBugs/SpotBugs, not the compiler. Python: no detection, relies on linters. Rust: Drop trait guarantees cleanup. Go: no detection (defer is voluntary). C#: Roslyn CA2000 warns but code still compiles. EK9: an unnamed closeable is closed by its statement and a named one must be a try-with-resources resource (compile-time error E81056), so abandoned resources cannot exist.

Keywords: E81056, transfer, try, leak, close, try-with-resources, resource, abandoned, ownership