How can my code use a closeable resource it does not own?

← Error Handling and Exceptions · Ref: Q1900

BORROW it. A closeable has exactly one owner - the try that acquired it, the statement that made it, or the object that holds it in a property - and only the owner's generated close ever closes it. Code that merely uses the resource borrows it in one of three ways, and none of them can close it.

1. AS AN ARGUMENT - the loan pattern. The owner keeps the resource and calls a function (or method) with it; the callee borrows it for that call only, and may not keep it (E81062) or close it (E81060):

  lend()
    -> reader as PageReader
    <- rtn as String: reader(page)

2. THROUGH A VIEW THE STANDARD LIBRARY PROVIDES. A TCP connection's input() and output() - and a TCP handler's 'input' and 'output' parameters - are a StringInput and a StringOutput, which have no 'operator close'. Hold or pass the view, never the connection: a registry of StringOutput views is how one connection's handler writes to all the others (Q1901). A view type of your OWN cannot be written: a class that keeps the resource in a property owns it, so it must close it (E81067) and must be given a NEW one (E81062) - it can never wrap a resource somebody else holds.

3. INSIDE THE TRY THAT OWNS IT. A dynamic class made in the try that captures the resource BY NAME borrows it for as long as the try runs; the try still closes it, once. Capturing a NEW value instead - '(draft: Page("fresh"))' - makes the dynamic class its owner, and an owner must declare its own close (E81067).

The program below lends a journal's page to a function, and lets a dynamic class read a try resource it captured by name. Each page is closed once, by its owner.

Example

defines module qa.errorhandling.borrowwithoutowning

  defines trait

    Reading
      read() as pure
        <- rtn as String?

  defines class

    Page
      name as String?

      default private Page()

      Page()
        -> label as String
        name: label

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

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

      default operator ?

    Journal
      page as Page?

      default private Journal()

      Journal()
        -> label as String
        page: Page(label)

      lend()
        -> reader as PageReader
        <- rtn as String: reader(page)

      default operator close

      default operator ?

  defines function

    PageReader() as abstract
      -> page as Page
      <- rtn as String?

    firstWords() is PageReader
      -> page as Page
      <- rtn as String: `lent ${page.read()}`

  defines program

    BorrowWithoutOwning()
      stdout <- Stdout()

      try
        -> journal <- Journal("journal")
        stdout.println(journal.lend(firstWords))

      try
        -> draft <- Page("draft")
        reader <- (draft) with trait of Reading as class
          override read() as pure
            <- rtn as String: `borrowed ${draft.read()}`
          default operator ?
        stdout.println(reader.read())

Common mistakes

E81067 — Capturing a NEW page makes the dynamic class its owner, and if you hold it, you close it: it would have to declare its own 'default operator close'. Capturing the try resource by name only borrows it. See ek9 -h E81067 for details.

Incorrect:

        reader <- (draft: Page("fresh")) with trait of Reading as class

Correct:

        reader <- (draft) with trait of Reading as class
Other ways to ask this
  • How do I borrow a resource without closing it?
  • Can I write a view class that wraps a connection or file my object holds?
  • Why can't I return a view type that wraps my closeable property?
  • How does a dynamic class use a try resource without owning it?

Coming from another language?

Java: any code holding a reference to an AutoCloseable may close it or keep it, and a wrapper class decides by convention whether it 'owns' what it wraps. C#: the same with IDisposable and leaveOpen flags. Rust: a borrow (&T) cannot outlive or drop its owner. EK9: a borrower is an argument, a non-closeable view, or a by-name capture inside the owning try - and a type that keeps a closeable is always its owner.

Keywords: borrow, StringOutput, dynamic class, E81062, capture, E81067, view, owner, loan, closeable