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

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: copy, close, default operator, shallow copy, operator close, E81065, closeable, :=:, property