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>, ...) generatedRules#
Assigning, passing or capturing a value copies it.
var b = agivesbits own storage; nothing done tobafterwards is visible througha. This is what makesconston aPoint, aListor aMapmean the value never changes, not just that the binding cannot be reassigned.Equals,Hashandcopyare generated for everytype, each only if every field supports it.==always compares content, because there is no identity to compare instead. The generated implementations answer==, aMapkey and aSetelement, and their members are members like any other:a.equals(b)anda.hash()are there wherever==and a key would work, with or withoutwith 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 forEqualslike any other operator asks for its trait:Does not compiletype 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 itcopyhas the shape of the constructor, with every field optional.a.copy(y: 5)answers a new value withychanged and every other field kept; aprivatefield cannot be passed tocopyfrom outside, for the same reason it cannot be passed to the constructor.Every argument of
copyis 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 thinga.copy(5)must not be allowed to mean.Does not compiletype Point { x: Int var y: Int = 0 } const a = Point(1, 2) const wrong = a.copy(5) // error: `copy` takes labeled arguments: `copy(x: ...)`Values that are equal must have equal hashes.
Hashis generated together withEquals, from the same fields in the same order, so the contract holds automatically instead of being an obligation on the writer.What copying costs is not part of the language. A small value is copied directly; the storage behind a
Listor aStringis 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 contenttype 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 valueRelated#
- Declaring a type - where
Equals,Hash,Showandcopyare introduced. - Construction - the constructor
copymirrors. - The generated Show - the other member every type gets for free.
- Shared types - where identity replaces
Equals,Hashandcopyinstead.