Copy and equality

Assigning, passing or capturing a value copies it, and Equals, Hash and copy are generated for a type without being written, each only if every field supports it.

A type has no identity: assigning it, passing it as an argument and capturing it in a closure all produce a copy, so two bindings never see the same storage change. Equals, Hash and copy follow from that without a line of code.

Example#

type Point {
  var x: Int
  var y: Int
}

const a = Point x: 1, y: 2
var b = a                       // A copy: changing `b` never changes `a`
b.x = 9

print(a == b)                   // prints false
print(a == Point(x: 1, y: 2))   // prints true
print a.copy(y: 5)              // prints Point(x: 1, y: 5)

== is Equals, generated and structural; the same holds for Hash, which is what lets a Point be a key of a Map or an element of a Set.

Syntax#

value == other                       Equals, generated
value.equals(other), value.hash()    the members of the generated Equals and Hash
value.copy(<field>: <value>, ...)    generated

Rules#

  1. Assigning, passing or capturing a value copies it. var b = a gives b its own storage; nothing done to b afterwards is visible through a. This is what makes const on a Point, a List or a Map mean the value never changes, not just that the binding cannot be reassigned.

  2. Equals, Hash and copy are generated for every type, each only if every field supports it. == always compares content, because there is no identity to compare instead. The generated implementations answer ==, a Map key and a Set element, and their members are members like any other: a.equals(b) and a.hash() are there wherever == and a key would work, with or without with Equals, Hash.

    type Point {
      x: Int
      y: Int
    }
    
    const a = Point x: 1, y: 2
    print a.equals(Point(x: 1, y: 2))
    print(a.hash() == Point(x: 1, y: 2).hash())

    A field that is a function breaks all three, because a function value has none of them, and == asks for Equals like any other operator asks for its trait:

    Does not compile
    type Button {
      label: String
      onClick: () => Void
    }
    
    const a = Button label: "OK", onClick: { print "clicked" }
    const b = Button label: "OK", onClick: { print "clicked" }
    print(a == b)
    // error: `Button` does not implement `Equals`, so `a == b` has no meaning for it
  3. copy has the shape of the constructor, with every field optional. a.copy(y: 5) answers a new value with y changed and every other field kept; a private field cannot be passed to copy from outside, for the same reason it cannot be passed to the constructor.

  4. Every argument of copy is labeled. It changes the fields it is given and keeps the rest, so a positional argument would quietly fill the first field of the type - which is the one thing a.copy(5) must not be allowed to mean.

    Does not compile
    type Point {
      x: Int
      var y: Int = 0
    }
    
    const a = Point(1, 2)
    const wrong = a.copy(5)
    // error: `copy` takes labeled arguments: `copy(x: ...)`
  5. Values that are equal must have equal hashes. Hash is generated together with Equals, from the same fields in the same order, so the contract holds automatically instead of being an obligation on the writer.

  6. What copying costs is not part of the language. A small value is copied directly; the storage behind a List or a String is shared until something writes to it. Both give the same answer to == and to every rule above, so nothing here depends on which one the implementation chose.

What this is not#

Structural equality is not identity, and there is no way to ask for identity on a value. Two values that hold the same data are ==, however they were constructed - there is no separate "same object" question for a type, because a type never has one.

const first = Point x: 1, y: 2
const second = Point x: 1, y: 2
print(first == second)          // true: same content
Does not compile
type Point {
  var x: Int
  var y: Int
}

const first = Point x: 1, y: 2
const second = Point x: 1, y: 2
print isSame(first, second)
// error: `isSame` compares identity, and a `Point` is a value
Esc