Why can't a closeable have an open() method that returns this - how do I acquire a resource?

← Error Handling and Exceptions · Ref: Q1720

A type that has an 'operator close' acquires its resource in its CONSTRUCTOR, and a try-with-resources header (or the statement that creates it) owns it from the moment it exists. There is no separate 'open' step, so there is nothing to forget and no half-open object. The two-step 'construct, then fluent open()' idiom is rejected because 'open() <- rtn as Res: this' hands back the object that the receiver expression already created: both the try and the temporary then own the same object, which is closed twice (E81057 - a closeable return must be a NEW instance).

Name it in a try when you need it across several statements:

  try
    -> named <- Tracker("named")
    stdout.println(named.describe())

Leave it unnamed for a one-shot read - the statement closes it:

  lines <- List() of String
  cat TextFile("data.txt").input() > lines

The program below acquires in the constructor, uses one tracker named in a try and one unnamed, and has a method that hands back a NEW tracker, which the caller's try then closes.

Example

defines module qa.errorhandling.acquireinconstructor

  defines class

    Tracker
      name as String?

      default private Tracker()

      Tracker()
        -> label as String
        stdout <- Stdout()
        name: label
        stdout.println(`opened ${name}`)

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

      again()
        <- rtn as Tracker: Tracker(name)

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

      default operator ?

  defines program

    AcquireInConstructorDemo()
      stdout <- Stdout()

      try
        -> named <- Tracker("named")
        stdout.println(named.describe())

      stdout.println(Tracker("unnamed").describe())

      try
        -> second <- Tracker("first").again()
        stdout.println(second.describe())

Common mistakes

E81057 — A method with a closeable return type must return a NEW instance; returning 'this' would give the caller the object that the receiver expression already owns, so it would be closed twice. Acquire in the constructor instead of a fluent open(). See ek9 -h E81057 for details.

Incorrect:

      again()
        <- rtn as Tracker: this

Correct:

      again()
        <- rtn as Tracker: Tracker(name)
Other ways to ask this
  • How do I write the Resource().open() pattern in EK9?
  • Why is a fluent open() on my closeable class error E81057?
  • Should a resource be acquired in its constructor or in a separate open() call?
  • What replaces 'file = File(path); file.open()' in EK9?

Coming from another language?

Java: new Resource().open() or a builder returning this leaves the construct-then-open window where an exception leaks the object. Python: __init__ then .open() has the same window; 'with' is optional. C++: RAII - acquire in the constructor, release in the destructor - is exactly this model. Rust: a constructor such as File::open returns the owned value. EK9: acquire in the constructor; a method returning a closeable must return a new instance (E81057).

Keywords: closeable, fluent, try, E81057, resource, close, constructor, RAII, acquire, ownership, open