Every language has a map; the differences are all in the corners. What happens on a missing key — a nil, an exception, a zero that looks like data? What does assignment share? Whose order does iteration follow? Mica’s answers are the language’s own rules applied without exception, and the missing-key answer is the one worth the trip: it depends on what you asked.

The example is Maps; it assumes dynamic arrays.

The verbs, by import and by address

imp
    Map, KeyList, Put, Get, GetOr, Has, Remove, Count, Keys : maps;

var
    stock : Map of (string, int64);

A Map of (K, V) is typed at both ends, the verbs live in the maps unit, and mutation travels by address — the same consent rule as everywhere else:

Put(address stock, "bolts", 250);
The verbs
  3 kinds in stock
  bolts after an update: 240 (still 3 kinds)

A repeated key replaces its value in place; the live count never grows on an update.

Two reading moods

Here is the corner most languages get wrong, answered by splitting the question. Get is for keys the program knows are there. A missing key is then a bug — first kingdom, as the error tutorials drew the line — and it stops the program at once, on every optimization tier:

Mica runtime failure: reason=map_key_missing (16)

No nil that dereferences three functions later, no default that masquerades as data. Has and GetOr ask first, for keys that are honestly optional, with the default visible at the call:

Asking first, or knowing
  do we carry rivets? 0
  rivets or a visible default: -1
  nuts, known to be there: 900

The programmer states which mood each read is in, and each mood keeps its promise. Go’s two-result comma-ok is opt-in — forget it and you get a zero value silently. Mica has no silent road.

Snapshots in insertion order

names := Keys(stock);
for i := 0 to (Length(names) as int64) - 1 do
    WriteLn("  %ls: %lld", names[i], Get(stock, names[i]));
Snapshots in insertion order
  bolts: 240
  nuts: 900

Keys and Values answer the surviving entries in insertion order — a deterministic walk, not a hash order that reshuffles between runs or releases. Each snapshot is itself a value the caller owns.

A map is a value

backup := stock;
Put(address stock, "bolts", 0);
Remove(address stock, "nuts");
A map is a value
  original: bolts 0, 1 kinds
  backup:   bolts 240, 2 kinds

The assignment deep-copies the entries, keys and values both. The original then changed twice, and the backup holds the world as it stood — no hidden sharing, no copy-on-write surprise, the value rule with no exception for containers.

What this does not do

  • No stock["bolts"] sugar. The verbs are imported functions; the bracket stays what it is everywhere — an array index. One fewer thing that looks cheap and is not.
  • Insertion order is the only order. There is no sorted view; sort a snapshot if you need one.
  • The deep copy is real work. Assigning a large map costs what copying a large map costs — the price of no hidden sharing, paid where it is visible.

Try it

git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/Maps run

Change the last Get(stock, ...) to ask for "washers" — removed earlier in the run — and meet map_key_missing at a named line. Then make the same read a GetOr and decide what the default should be; having to decide is the feature.

Next

Bit records — the other end of the data spectrum: named, typed fields packed into exactly N bits, and the case that dispatches on binary patterns.