Keys & Equality
Structs as Map and Set keys: integer-only structs
compare and hash for free; float-bearing ones declare their own pair.
A struct whose leaves are all integers (recursively, through nested
structs and FixedArray) gets a derived
== and hash: leaf-wise comparison in schema order, and a
deterministic FNV-1a over the leaves. Such structs compare with
==/!= anywhere and key a Map or
Set with no declarations at all.
struct Cell {
x: i32 = 0;
y: i32 = 0;
constructor(x: i32, y: i32) { this.x = x; this.y = y; }
}
struct Snapped {
x: f32 = 0;
y: f32 = 0;
constructor(x: f32, y: f32) { this.x = x; this.y = y; }
operator==(a: Snapped, b: Snapped): bool {
return i32(a.x * 16) == i32(b.x * 16) &&
i32(a.y * 16) == i32(b.y * 16);
}
readonly hash(): u32 {
return u32(i32(this.x * 16)) * 31 + u32(i32(this.y * 16));
}
}
export function demo(): i32 {
let grid = new Map<Cell, i32>();
grid.set(Cell(3, 4), 7); // derived == and hash
let snapped = new Map<Snapped, i32>();
snapped.set(Snapped(1.0, 2.0), 5); // declared pair: quantized
let hit = snapped.has(Snapped(1.01, 2.01)) ? 1 : 0;
return (grid.get(Cell(3, 4)) as i32) + hit; // 7 + 1
}
Float-bearing structs never derive — -0.0 == +0.0 yet the
bits differ, and NaN would make a key unfindable — so they declare the pair
explicitly: operator== plus a readonly hash(): u32.
Declaring one without the other is a compile error (a custom equality that
disagrees with a derived hash is the classic unfindable-key bug), and
coherence — a == b implies equal hashes — is your contract,
like C++ and Dart. Deviations from stock expectations, all deliberate:
!=always derives as the negation of==; declaringoperator!=is an error.- The derived hash folds every leaf, so two keys equal under a
declared
==but different in ignored fields would disagree — which is exactly why declaring==turns the derived hash off. - Struct keys copy in and out like everything else; mutating a local
after
setnever edits the stored key. - Key identity never inherits — a derived struct declares its own pair or derives from its own integer leaves; an inherited pair would ignore the derived fields (see Inheritance).