A hash lets you find a record by its key in the same time whether you have 10 records or 10 million — there is no searching through a list, just a direct jump to the answer. Unlike sorted and index, a hash has no meaningful order — you use it purely for fast lookups. List the key fields in brackets: hash<Type[field]>. Combine a hash with a vector when you want both fast lookup and a stable iteration order.
struct Keyword {
name: text
}
struct Data {
h: hash < Keyword[name] >
}
Combining Hash and Vector
Pairing a hash with a vector on the same record type gives you the best of both worlds: the hash for instant lookup by key, and the vector for iterating in insertion order.
struct Count {
t: text,
v: integer
}
struct Counting {
entries: vector < Count >,
lookup: hash < Count[t] >
}
fn fill(c: Counting) {
c.entries =[
{t: "One", v: 1 },
{t: "Two", v: 2 },
{t: "Three", v: 3 },
{t: "Four", v: 4 }
]
}
fn main() {
Basic Lookup
Fill a hash the same way you fill a vector. Look up by key with square brackets. A found record is truthy; a missing key returns null.
d = Data { };
d.h =[ {name: "one" }, {name: "two" }];
d.h +=[ {name: "three" }, {name: "four" }];
assert(d.h["three"], "Key exists");
assert(!d.h["None"], "Missing key returns null");
Hash + Vector Together
Here entries is the vector (keeps insertion order) and lookup is the hash (fast access). Both fields point to the same records — adding to one automatically updates the other.
c = Counting { };
fill(c);
assert(c.lookup["Three"].v == 3, "Hash lookup: Three");
assert(c.lookup["One"].v == 1, "Hash lookup: One");
assert(!c.lookup["Five"], "Missing key returns null");
Iterate in insertion order via the vector; look up by name via the hash. The vector also supports #first, #count, and #remove inside the loop.
add = 0;
for item in c.entries {
add += item.v;
}
assert(add == 10, "Sum via vector iteration: {add}");
Removing a Key
Assigning null to a hash subscript removes that element. Removing a key that is not present is a safe no-op.
d.h["three"] = null;
assert(!d.h["three"], "three was removed");
assert(d.h["one"], "one still present");
d.h["missing"] = null;
no-op; does not panic
Iterating a Hash
for e in h { ... } walks the hash in ascending key order. The loop variable has the same shape as a sorted<T> iteration — a reference<T> with #index, #count, and #first available. Multi-field keys sort lexicographically.
Under the hood the compiler builds a one-shot sorted snapshot of the hash's record-numbers and walks it, so iteration cost is O(n) + O(n log n) per for, not amortised across calls. For a hot loop, pair the hash with a vector<T> or sorted<T[k]> on the same record type and iterate the companion collection.
e#remove is rejected at compile time — the snapshot is detached from the hash, so removing from it would not actually remove from the hash. Use h[key] = null to remove an entry.
sum = 0;
for e in c.lookup { sum += e.v; }
assert(sum == 10, "Sum via hash iteration: {sum}");
}