E81063: a closeable slot is assigned once - a property in its own constructor or initialiser, a returning variable once on each path (declared unset), a try resource only by its try; re-pointing it drops the resource it held
What went wrong
A closeable SLOT is assigned where it may already hold a resource. Every closeable has one owner and is closed once, so each slot that owns one is assigned once:
- A closeable PROPERTY is set while its object is constructed: in its own initialiser or its own type's constructor, once on any path. Assigning it outside the constructor, through another object ('other.conn'), or in the constructor after it already holds one (its initialiser ran, or an earlier statement on some path set it) is this error.
- A closeable RETURNING variable (of a function, method, operator, dynamic function, or a switch/try/while/for expression) that is initialised at its declaration is never assigned again. One declared unset is assigned at most once on any path: not twice in a row, not in one arm of an 'if' and again after it, not in a 'try' body (other than as its last statement) and again in its 'catch', and not in a loop body, which can run again.
- A try-with-resources RESOURCE is never assigned again: its try closes only what it holds at the end.
Re-pointing drops the resource the slot held, and nothing ever closes it. The guarded form ':=?' does not help: a closeable that failed or was closed is present but UNSET, so ':=?' replaces it - and still drops it.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 1841 (How do I reconnect or replace a resource my object holds?)
- ek9 -q 1720 (Why can't a closeable have an open() method that returns this - how do I acquire a resource?)
How to fix it
For a property, acquire a fresh resource in a try each time one is needed, rather than replacing one held in a property - a 'reconnect()' that assigns a new connection to the property becomes a loop of tries, each with its own resource ('callTwice' in the [EXAMPLE] section below).
For a returning variable, declare it unset and assign it once in each arm, rather than giving it a default resource and replacing it in an arm ('choose' in the same [EXAMPLE]).
For a try resource, start a new try for the new resource.
Or, when an object needs a different resource for its whole life, make a new object - its constructor acquires the resource and its 'operator close' releases it.
Common causes
- A reconnect(), reset() or failOver() method that assigns a new connection.
- 'conn :=? Connection(..)' to "open it if it is not open".
- A property with an initialiser that the constructor assigns again.
- Assigning another object's closeable property.
- A returning variable given a default resource and then replaced in an arm.
- A returning variable assigned in a loop, keeping the last attempt.
- Replacing a try resource part way through its try.
See also
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E81063: a closeable slot is assigned once - a property in its own constructor or initialiser, a returning variable once on each path (declared unset), a try resource only by its try; re-pointing it drops the resource it held
#!ek9 defines module error.e81063.bad 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}`) Client conn as Res? Client() conn: Res("first") reconnect() conn := Res("second") default operator ? default operator close defines function choose() -> choice as Boolean <- rtn as Res: Res("default") if choice rtn: Res("chosen")
Fixed: Use a fresh resource per try; give a returning variable one value on each path
#!ek9 defines module error.e81063.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}`) defines function callTwice() <- rtn as String: String() for attempt in 1 ... 2 try -> conn <- Res(`attempt ${attempt}`) rtn: conn.read() choose() -> choice as Boolean <- rtn as Res? if choice rtn: Res("chosen") else rtn: Res("default")
Questions and answers
These Q&A entries show E81063 and its fix: