Can a type that holds a closeable resource use 'default operator :=:'?

← Error Handling and Exceptions · Ref: Q1843

No. The generated copy is shallow: it copies each property by reference. On a type that holds a closeable property, after 'first :=: second' both objects would hold second's resource, both would close it, and the resource first held would never be closed. No generated copy can be right for a resource, so 'default operator :=:' - written alone, or by the bare 'default operator', which includes it - is error E81065 on such a type.

Ask only for the default operators the type needs ('default operator ?', 'default operator $', ...). If the type really must be copied, write 'operator :=:' yourself and copy only the values that are not resources: each object keeps, and later closes, the resource it acquired itself.

The program below copies one holder over another with a hand-written copy: the first takes the second's label but keeps its own resource, and each resource is closed exactly once.

Example

#!ek9
defines module qa.errorhandling.nodefaultcopyofaresourceholder

  defines class

    Resource
      name as String?

      default private Resource()

      Resource()
        -> text as String
        name: text

      label() as pure
        <- rtn as String: name

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

      default operator ?

    Holder
      label as String?
      held as Resource?

      default private Holder()

      Holder()
        -> text as String
        label: String(text)
        held: Resource(`${text}-resource`)

      describe() as pure
        <- rtn as String: `${label} holding ${held.label()}`

      <?- Copies only what is not a resource: each holder keeps the resource it acquired -?>
      operator :=:
        -> other as Holder
        label :=: other.label

      default operator close

      default operator ?

  defines program

    CopyAResourceHolder()
      stdout <- Stdout()

      try
        ->
          first <- Holder("first")
          second <- Holder("second")
        first :=: second
        stdout.println(second.describe())
        stdout.println(first.describe())

Common mistakes

E81065 — The generated copy would copy the closeable property by reference, so both holders would close one resource. Write ':=:' and copy only what is not a resource. See ek9 -h E81065 for details.

Incorrect:

      default operator :=:

Correct:

      operator :=:
        -> other as Holder
        label :=: other.label
Other ways to ask this
  • Why is 'default operator' an error on my class that holds a connection?
  • How do I copy an object that owns a resource?
  • Why can't the generated copy operator copy a closeable property?
  • What does E81065 mean?

Coming from another language?

Java: clone() of an object with a Socket field shares the socket, and closing both clones closes it twice. C++: the implicitly generated copy constructor copies a raw handle the same way, which is why the rule of three/five exists. Rust: a type holding a File is not Copy and Clone must be written. EK9: the generated copy is refused on a type that holds a closeable (E81065); write ':=:' and copy only what is not a resource.

Keywords: :=:, E81065, close, closeable, copy, default operator, operator close, property, shallow copy