How do I let other code use a resource my object owns without letting it close or keep it?

← Error Handling and Exceptions · Ref: Q1740

When an object owns a closeable in a property, other code must not receive it as a return value (E81057 - a closeable return must be a new instance) or store it in a trait or Any slot (E81059). Reverse the direction: LEND it. Give the owner a method that takes a function, and have the owner call that function with the resource it holds. The resource is passed as an ARGUMENT, so it is borrowed for that call only; the owner's generated close still closes it.

Declare the function type with a parameter whose type is NOT closeable (a trait without 'operator close', or StringInput for a file's lines), so the callee can read but never close. Then the owner:

  lend()
    -> user as PageUser
    <- rtn as Integer: user(notebook)

This is the pattern to use for a file held as a CloseableStringInput property, because (unlike a TCP connection's input() and output(), which return a separate StringInput view) a file's closeable has no view to return. The loan is one of three ways to use a resource you do not own; the other two - a view the standard library provides, and a by-name capture inside the owning try - are in Q1900. The program below owns a closeable Notebook, lends it to a function that counts lines, and lets the try close it afterwards.

Example

defines module qa.errorhandling.loanpattern

  defines trait

    Pages
      next() as abstract
        <- rtn as String?

      hasNext() as abstract
        <- rtn as Boolean?

  defines class

    Notebook with trait of Pages
      lines as List of String?
      position as Integer: 0

      default private Notebook()

      Notebook()
        -> first as String
        lines: List() of String
        lines += first
        lines += "second"
        lines += "third"

      override next()
        <- rtn as String: lines.getOrDefault(position, "")
        position++

      override hasNext()
        <- rtn as Boolean: position < length lines

      operator close as pure
        stdout <- Stdout()
        stdout.println("closed notebook")

      default operator ?

    Owner
      notebook as Notebook?

      default private Owner()

      Owner()
        -> first as String
        notebook: Notebook(first)

      lend()
        -> user as PageUser
        <- rtn as Integer: user(notebook)

      default operator close

      default operator ?

  defines function

    PageUser() as abstract
      -> pages as Pages
      <- rtn as Integer?

    countPages() is PageUser
      -> pages as Pages
      <- rtn as Integer: 0
      while pages.hasNext()
        if pages.next()?
          rtn++

  defines program

    LoanPatternDemo()
      stdout <- Stdout()

      try
        -> owner <- Owner("first")
        stdout.println(`pages: ${owner.lend(countPages)}`)
        stdout.println("owner still holds the notebook")
Other ways to ask this
  • What is the loan pattern for a closeable property?
  • How do I lend a file I hold when a StringInput view is not available?
  • Why does returning my CloseableStringInput property give E81057 or E81059?
  • How do I let a caller read the lines of a file my class keeps open?

Coming from another language?

Java: a getter returns the held Reader and any caller can close it, or keep it past the owner's lifetime. Rust: a closure or function taking &Reader borrows it for the call only. Swift: a non-escaping closure parameter has the same lifetime limit. Python: a context manager that yields the handle lets the body close it. EK9: the owner calls a supplied function with the resource - an argument is a loan.

Keywords: lend, loan, StringInput, close, function parameter, E81057, E81059, ownership, TextFile, closeable, property, borrow