E81059: a closeable value may only be stored where the declared type is closeable

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

What went wrong

A closeable value (its type has an 'operator close') is stored into a variable, a property or the returning variable whose DECLARED type has no 'close' - a trait such as IO or StringInput, or Any. Closeability cannot be erased by a store.

Nothing holding the value through that name can ever close it. The statement that created it sees it stored, so it does not close it either, and E81056 does not apply because the declared type is not closeable. It would leak.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 1640 (Can I keep a closeable in a variable of a trait or Any type?)
  • ek9 -q 1520 (When does EK9 close a closeable value, and where may I keep one?)

How to fix it

Keep the closeable type, in a try-with-resources resource (good.ek9 in the [EXAMPLE] section below). Or, when the code only needs the narrower type, do not store it - pass it as an argument. A parameter of a non-closeable type borrows a view, and the statement (or the try) that owns the value still closes it (good_not_a_store.ek9 below shows that, and each other form that is not a store).

What is not a store

  • An ARGUMENT: passing a CloseableStringInput to a StringInput parameter lends a view; the owner still closes it.
  • A slot declared as the Closeable trait, or as the closeable type itself.
  • A standard stream (Stdin, Stdout, Stderr): borrowed, never closed.

Common causes

  • Declaring a variable, property or returning variable with the trait the code reads through (StringInput, IO, a trait of your own) instead of the closeable type.
  • Aliasing a try resource into a variable declared outside the try.

See also

  • E81059: Closeable stored where it cannot be closed (this error)
  • E81056: Closeable held in a named local (must be a try resource)
  • E81062: Closeable slot given a value that is not new (who owns what)

Examples

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

Fails with E81059: a closeable value may only be stored where the declared type is closeable

#!ek9
defines module error.e81059.bad

  defines trait

    Labelled
      label() as pure
        <- rtn as String?

  defines class

    LabelledResource with trait of Labelled
      text as String?

      default private LabelledResource()

      LabelledResource()
        -> initialText as String
        text: initialText

      override label() as pure
        <- rtn as String: text

      default operator ?

      operator close as pure
        require true

    <?- BAD: a property declared as the non-closeable trait, initialised with a closeable -?>
    TraitHolder
      held as Labelled: LabelledResource("held")

      default operator ?

    <?- BAD: a property re-pointed at a closeable, by name and through 'this' -?>
    LateHolder
      held as Labelled?

      LateHolder()
        held: LabelledResource("late")

      replaceHeld()
        this.held := LabelledResource("replaced")

      default operator ?

  defines function

    <?- BAD: a local declared as the non-closeable trait -?>
    storedAsTrait()
      labelled as Labelled: LabelledResource("trait")
      require labelled?

    <?- BAD: a local declared as Any -?>
    storedAsAny()
      anything as Any: LabelledResource("any")
      require anything?

    <?- BAD: a built-in closeable input stored as the non-closeable StringInput -?>
    storedAsStringInput()
      input as StringInput: TextFile("lines.txt").input()
      require input?

    <?- BAD: the returning variable declared as the non-closeable trait - the caller could never close it -?>
    returnedAsTrait()
      <- rtn as Labelled: LabelledResource("returned")

    <?- BAD: a declared, unset local later assigned a closeable -?>
    assignedLater()
      labelled as Labelled?
      labelled := LabelledResource("later")
      require labelled?

    <?- BAD: an assign-if-unset is a store too -?>
    assignedIfUnset()
      labelled as Labelled?
      labelled :=? LabelledResource("guarded")
      require labelled?

    <?- BAD: a try resource aliased into a non-closeable local outlives its try -?>
    aliasOfTryResource()
      outer as Labelled?
      try
        -> resource <- LabelledResource("aliased")
        outer := resource
      require outer?

Fixed: Keep the closeable type - in a try

#!ek9
defines module error.e81059.good

  defines function

    firstLine()
      -> path as FileSystemPath
      <- rtn as String: String()
      try
        -> input <- TextFile(path).input()
        if input.hasNext()
          rtn: input.next()

Fixed: What is not a store: a lent argument, a Closeable-typed try resource or return, an owning property

#!ek9
defines module error.e81059.good.not.a.store

  defines trait

    Described
      description() as pure
        <- rtn as String?

  defines class

    DescribedResource with trait of Described, Closeable
      text as String?

      default private DescribedResource()

      DescribedResource()
        -> initialText as String
        text: initialText

      override description() as pure
        <- rtn as String: text

      override operator close as pure
        require true

      default operator ?

    PlainDescription with trait of Described
      text as String?

      default private PlainDescription()

      PlainDescription()
        -> initialText as String
        text: initialText

      override description() as pure
        <- rtn as String: text

      default operator ?

    <?- OK: a property declared as the closeable type is owned by its object, and closed by it -?>
    ResourceOwner
      owned as DescribedResource?

      ResourceOwner()
        owned: DescribedResource("owned")

      default operator close

      default operator ?

  defines function

    describe() as pure
      -> described as Described
      <- rtn as String: described.description()

    <?- OK: an unnamed closeable passed as an argument - lent, the statement still closes it -?>
    unnamedArgument()
      require describe(DescribedResource("argument"))?

    <?- OK: a try resource passed as an argument - lent, the try still closes it -?>
    tryResourceArgument()
      try
        -> resource <- DescribedResource("try")
        require describe(resource)?

    <?- OK: a try resource declared as the Closeable trait keeps its close -?>
    tryResourceAsCloseable()
      try
        -> resource as Closeable: DescribedResource("closeable")
        require resource?

    <?- OK: returning as the closeable type hands ownership to the caller -?>
    returnedAsResource()
      <- rtn as DescribedResource: DescribedResource("returned")

    <?- OK: returning as the Closeable trait keeps the close operator -?>
    returnedAsCloseable()
      <- rtn as Closeable: DescribedResource("closeable")

    <?- OK: a non-closeable value stored as the trait -?>
    plainAsTrait()
      described as Described: PlainDescription("plain")
      require described?

    <?- OK: a standard stream is borrowed, not owned, so it may be held as StringInput -?>
    standardInputAsStringInput()
      input as StringInput: Stdin()
      require input?

Questions and answers

These Q&A entries show E81059 and its fix: