16 — Equality

Previous: Generics, traits, and derive

日本語版: 16_equality.vibe.md

== compares by value. Two arrays with equal contents are equal, two structs with equal fields are equal, and nothing here quietly compares addresses instead.

One edge is worth knowing, and it does not answer wrongly either: a generic T with no Eq witness is a compile error. It is below.

The ordinary cases

Scalars, tuples, structs and enums with derive(Eq), Bytes by content, and arrays.

struct Point {
  x: Int; y: Int
} derive (Eq)

fn same_ints(a: Array[Int], b: Array[Int]) -> Bool {
  a == b
}

fn main with Console {
  println("lits = \{[1, 2] == [1, 2]}")
  let a = [
    1,
    2
  ]
  let b = [
    1,
    2
  ]
  println("lets = \{a == b}")
  println("fn   = \{same_ints(a, b)}")
  println("tuple = \{([1, 2], 0) == ([1, 2], 0)}")
  println("struct = \{Point::{ x: 1, y: 2 } == Point::{ x: 1, y: 2 }}")
}
lits = true
lets = true
fn   = true
tuple = true
struct = true

Bytes is content equality too, including as a tuple element or a derive(Eq) field.

The cases people expect to be exceptions

Arrays whose elements are not scalars, arrays that arrive as a function's return value, and arrays that started out empty are compared by value too. The last one is worth running: pushing into one of two empty arrays gives the right answer, not a stale one.

fn mk() -> Array[Int] {
  [
    1,
    2
  ]
}

fn main with Console {
  let pairs: Array[(Int, Int)] = [(1, 2)]
  let same: Array[(Int, Int)] = [(1, 2)]
  let other: Array[(Int, Int)] = [(1, 3)]
  println("non-scalar elements = \{pairs == same}, differ = \{pairs == other}")
  println("function returns    = \{mk() == mk()}")
  let xs: Array[Int] = []
  let ys: Array[Int] = []
  println("empty and empty     = \{xs == ys}")
  Array::push(xs, 1)
  println("after one push      = \{xs == ys}")
  Array::push(ys, 1)
  println("after both          = \{xs == ys}")
  let us = []
  let vs = []
  Array::push(us, 1)
  Array::push(vs, 2)
  println("no annotation       = \{us == vs}")
}
non-scalar elements = true, differ = false
function returns    = true
empty and empty     = true
after one push      = false
after both          = true
no annotation       = false

us and vs carry no annotation, and they compare by content all the same: an unannotated let xs = [] takes its element type from the Array::push calls that fill it (#2157) — as long as the pushed value says what it is. A literal does, and so does an array, tuple or struct of literals, or an if whose branches agree.

Push a name or a call result instead and the binding gets no element type. Comparing two such arrays once both are non-empty fails at run time rather than answering by address or by length. The annotation is the fix, and it is the reason xs and ys above carry one.

A struct that takes type parameters says what it is only for some type arguments. One Box::equals is generated for the whole struct, and it compares the field that came from T without knowing what T was — so whether that is content equality depends entirely on T. It is, for Int, Bool, Unit and String; it is address equality for Double, Bytes, and for any array or struct. So Box[Int]::{ value: 1 } resolves and Box[Double]::{ value: x } does not. Annotating does not help with the second group — that is a known defect being fixed separately, and the run-time failure is there to keep you from meeting it by accident.

The compile-time edge: a generic T with no witness

Inside fn f[T: Eq](a: T, b: T), the element type is gone by the time code is generated, so == is answered by the Eq witness the caller passes. For a type that has one, that works and gives the structural answer:

fn eq2[T: Eq](a: T, b: T) -> Bool {
  a == b
}

fn main with Console {
  println("Int    same = \{eq2(1, 1)}, differ = \{eq2(1, 2)}")
  println("String same = \{eq2("x", "x")}, differ = \{eq2("x", "y")}")
}
Int    same = true, differ = false
String same = true, differ = false

For a type that has no Eq witness, there is nothing to pass, and the call is rejected:

// skip: this is a compile error, shown for the message it produces
fn eq2[T: Eq](a: T, b: T) -> Bool { a == b }

fn main with Console {
  println("\{eq2([1], [1])}")
}
no impl `Eq` for `Array[Int]`

That is the whole of it. Between the two edges you are always told — at compile time for the missing witness, and by a trap for the unannotated empty array — and neither one silently answers by address.

Next: Concurrency.