E81075: a try resource is closed when its try ends, so it must not be given to a call that keeps it - keep the resource inside the try, pass a value taken from it, or make the holder a closeable that owns it

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

What went wrong

A try resource - a closeable named in a 'try' - is given to a call that KEEPS it, for example 'keepIn(kept, res)' where 'keepIn' adds its argument to the list it is also given, inside 'try -> res <- Res("x")'. The try closes 'res' when it ends; the list still holds it, and after the try it printed '[Res(x, state=closed)]'.

This is the rule for an unnamed closeable (E81072), applied to a named one: a callee that keeps its argument - adds it to a collection another argument holds, stores it in a property, captures it in a dynamic function or class that outlives the call, or hands it to a call that does - leaves a keeper holding a closed resource. A constructor is read the same way: 'Holder(res)', where Holder's constructor stores 'res' in a property that cannot close it, leaves a Holder holding a closed resource wherever the Holder goes. And a call ON the resource that stores the resource ('res.registerIn(items)', whose body is 'items += this') is this error when the argument it stores it in is a variable declared outside the try, or a property; an argument made inside the try ('server.whenReady(handler)' with a handler made in the try) dies with it, and a function given as a handler is called with the resource, not a store. What a callable written in EK9 keeps is read from its body, over every override a call through a trait or abstract method may run; what a built-in keeps is what it declares; the operand of a compound assignment ('kept += res') is the argument of its target's operator. The same holds when the resource is handed back inside a value that is then kept: 'readers += readerOf(res)' where 'readerOf' returns a dynamic class that captures 'res' reads the resource after it is closed.

Only a callee that DEFINITELY keeps the argument is reported. One that only reads it, hands it back to be used inside the try, or is an abstract method nothing implements (it may keep any argument, but nothing can say it does) is not: the resource is a loan, and what it was handed back in is judged where that value is kept in turn.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)

How to fix it

Keep the resource inside the try: do the work that needs it there, and let the keeper hold what the work produced.

Or pass a VALUE taken from it - 'kept += res.tagText()' keeps a String, never the resource (good_fix.ek9 in the [EXAMPLE] section below).

Or make the holder a closeable that owns the resource: a class with the resource as a property, closed by its own 'operator close', and constructed with a NEW resource (Contract 14) - the holder, not the try, then decides how long the resource lives.

Common causes

  • Registering a connection or handle in a list, a registry or a cache while it is open in a 'try', to use it later.
  • Giving a try resource to a helper that adds it to a collection passed in.
  • Wrapping a try resource in a dynamic function or class that is returned or stored for later.

See also

  • E81075: A try resource outlives its try (this error)
  • E81072: An unnamed closeable kept by what it is given to
  • E81062: A closeable slot takes only a new value
  • E81056: Closeable held in a named local (must be a try-with-resources resource)

Examples

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

Fails with E81075: a try resource is closed when its try ends, so it must not be given to a call that keeps it - keep the resource inside the try, pass a value taken from it, or make the holder a closeable that owns it

#!ek9
defines module error.e81075.bad

  defines class

    Base as open
      tag as String: String()

      Base()
        -> tag as String
        this.tag :=: tag

      tagText() as pure
        <- rtn as String: tag

      default operator ?

    Res extends Base

      Res()
        -> label as String
        super(label)

      default operator ?

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

  defines function

    keepIn()
      ->
        kept as List of Base
        item as Base

      kept += item

    collect()
      <- rtn as Integer: 0
      kept <- List() of Base
      try
        -> res <- Res("x")
        keepIn(kept, res)
      rtn: length kept

Fixed: Keep a value taken from the resource, not the resource that its try closes

#!ek9
defines module error.e81075.good.fix

  defines class

    Base as open
      tag as String: String()

      Base()
        -> tag as String
        this.tag :=: tag

      tagText() as pure
        <- rtn as String: tag

      default operator ?

    Res extends Base

      Res()
        -> label as String
        super(label)

      default operator ?

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

  defines function

    keepIn()
      ->
        kept as List of String
        item as Base

      kept += item.tagText()

    collect()
      <- rtn as Integer: 0
      kept <- List() of String
      try
        -> res <- Res("x")
        keepIn(kept, res)
      rtn: length kept

Fixed: Make the holder a closeable that owns the resource and closes it

#!ek9
defines module error.e81075.good

  defines class

    Res
      name as String?

      default private Res()

      Res()
        -> label as String
        name: label

      default operator ?

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

    Holder
      held as Res?

      default private Holder()

      Holder()
        -> label as String
        held: Res(label)

      describe() as pure
        <- rtn as String: "holder"

      default operator ?

      default operator close

  defines function

    useHolder()
      <- rtn as String: String()
      try
        -> holder <- Holder("x")
        rtn: holder.describe()