E81061: a closeable type is never a type argument - hold it as a property of a type you own (closed in its 'operator close'), acquire it in a try, or lend it as a parameter

Category: E81xxx-E83xxx Test configuration, execution and coverage · Reported in phase: EXPLICIT_TYPE_SYMBOL_DEFINITION · Classification: CLOSEABLE_AS_TYPE_ARGUMENT · All error codes

What went wrong

A closeable type (its type has an 'operator close') is used as a type argument of a generic type or a generic function: List of TCP, Optional of Res, Dict of (String, HTTP), a Box of Res of your own, Acceptor of TCP. It is refused whether the type argument is written or inferred - [res], Optional(res), {"key": res} and a generic function called with a closeable are all the same parameterisation. A generic's 'T constrain by Res', where Res is closeable, is refused at its declaration for the same reason.

A closeable value has exactly one owner, and the compiler must see that owner close it: a try-with-resources resource, a property closed in its type's 'operator close', or the caller of a callable that returns a new one. A generic type or function has no such owner. Nothing would ever close what it holds, or it would close a value somebody else still owns.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1780 (Can I put a closeable resource in a List, an Optional or my own generic type?)
  • ek9 -q 1740 (How do I let other code use a resource my object owns without letting it close or keep it?)
  • ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)
  • ek9 -q 1901 (How do I broadcast a message to every open connection, like a chat server, when I cannot keep a list of connections?)

How to fix it

A closeable can be a PARAMETER type - a loan, and the lender keeps ownership - but never a type argument. Pick the shape that matches what the code needs:

  1. Hold it as a property of a type you own, and let that type's close close it - a 'Pair' holding two Res properties, closed by its generated close, rather than a List of Res (the [EXAMPLE] section below)
  2. Acquire it in a try where it is used, rather than wrapping it in an Optional
  3. Lend it as a parameter instead of collecting it - 'use(res)' from inside the owning try, rather than '[res]' (the same [EXAMPLE])

Common causes

  • Collecting several connections or files in a List or Dict to close them later in a loop.
  • Wrapping a resource in an Optional to say "may not be open".
  • A generic holder or cache class parameterised with a resource type.
  • A callback typed as a generic function type such as Acceptor of TCP; declare a non-generic function type that takes the resource as its parameter instead (as TCPReadyHandler does).

See also

  • E81061: Closeable type as a type argument (this error)
  • E81056: Closeable held in a named local (must be a try resource)
  • E81057: Closeable return is not a new instance
  • E81059: Closeable stored where it cannot be closed

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E81061: a closeable type is never a type argument - hold it as a property of a type you own (closed in its 'operator close'), acquire it in a try, or lend it as a parameter

#!ek9
defines module error.e81061.bad

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> initialName as String
        name: initialName

      default operator ?

      operator close as pure
        require true

    Box of type T
      default Box() as pure

      Box() as pure
        -> seed as T
        require seed?

      default operator ?

    <?- BAD: a property holding a list of closeables -?>
    ListHolder
      held as List of Res?

      default operator ?

    <?- A type closeable only through its super, declared before that super -?>
    SubRes extends BaseRes
      default SubRes()

      default operator ?

    BaseRes as open
      default BaseRes()

      default operator ?

      operator close as pure
        require true

    <?- VALID: a closeable held as a property of a type you own, closed in its operator close -?>
    Owner
      held as Res?

      default private Owner()

      Owner()
        -> name as String
        held: Res(name)

      default operator ?

      default operator close

  defines function

    <?- BAD: a parameter typed as a list of closeables -?>
    listParameter()
      -> resources as List of Res
      require resources?

    <?- BAD: a nested type argument -?>
    nestedListParameter()
      -> resources as List of List of Res
      require resources?

    <?- BAD: an Optional of a built-in closeable as the returning variable -?>
    optionalReturn() as abstract
      <- rtn as Optional of TCP?

    <?- BAD: a closeable as a Dict value type -?>
    dictParameter()
      -> servers as Dict of (String, HTTP)
      require servers?

    <?- BAD: a user generic parameterised with a closeable -?>
    boxConstruction()
      boxes <- Box() of Res
      require boxes?

    <?- BAD: a generic function type parameterised with a closeable -?>
    acceptorParameter()
      -> handler as Acceptor of TCP
      require handler?

    <?- BAD: an empty list of a built-in closeable input -?>
    listConstruction()
      inputs <- List() of CloseableStringInput
      require inputs?

    <?- BAD: the Closeable trait itself, and a type closeable only through its super -?>
    closeableTraitParameter()
      -> resources as List of Closeable
      require resources?

    inheritedCloseParameter()
      -> resources as Optional of SubRes
      require resources?

    <?- VALID: the standard streams have a close operator but are never closed, so they are not closeable here -?>
    standardOutputs()
      -> outputs as List of Stdout
      require outputs?

    <?- VALID: a closeable lent as a parameter -?>
    lentParameter()
      -> res as Res
      require res?

    <?- VALID: a generic of a non-closeable type -?>
    nonCloseableList()
      -> names as List of String
      require names?

Fixed: Hold the closeable in a property of a type you own, or acquire it in a try and lend it

#!ek9
defines module error.e81061.good

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

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

      default operator ?

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

    Pair
      first as Res?
      second as Res?

      default private Pair()

      Pair()
        -> label as String
        first: Res(`${label} one`)
        second: Res(`${label} two`)

      default operator ?

      default operator close

  defines function

    use()
      -> res as Res
      <- rtn as String: res.read()

    lend()
      <- rtn as String?
      try
        -> res <- Res("lent")
        rtn: use(res)

Questions and answers

These Q&A entries show E81061 and its fix: