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
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
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()