Nearly every language gives you records and arrays. The differences hide in three questions that sound too small to matter: who picks the first index, is a matrix one thing or an array of arrays, and what exactly happens when an index is wrong.

Mica’s answers: you pick, one thing, and it depends on when the compiler can know — which is the interesting one, and this article ends on it.

The example is examples/Aggregates. Build and run it:

make -C examples/Aggregates run

This article builds directly on values and pointers: everything here is a value, and the copy rule you saw there reaches every shape on this page.

A shape is named before it is used

All the shapes in the example are declared once, in type:

type
    Point = record
        x, y : int64;
    end;

    Row  = array[-2..2] of int64;
    Grid = array[1..2, -1..1] of int64;
    Trio = array[0..2] of Point;

    Path = record
        name  : string;
        count : int64;
        pts   : Trio;
    end;

Note the field pts : Trio. An inline array[0..2] of Point cannot stand in a field or a var — the compiler refuses the spelling — and the rule is not pedantry: a shape declared once and named everywhere is a fact that exists in exactly one place. When Trio grows a fourth element, there is no second declaration somewhere that still says three.

Bounds are yours, and the type remembers them

Row runs from −2 to 2. Negative bounds are as ordinary as zero-based ones, because the bounds are simply part of the type — and Low and High read them back:

r := Ramp(3);
WriteLn("  Row runs %d to %d", Low(r), High(r));

for row := Low(r) to High(r) do
    WriteLn("  r[%lld] = %lld", row, r[row]);
  Row runs -2 to 2
  r[-2] = -6
  r[-1] = -3
  r[0] = 0
  r[1] = 3
  r[2] = 6

The loop and the array read their range from the same declaration, so the walk cannot be off by one against the storage. If you have written for (i = 0; i <= n; i++) over an n-element buffer once in your life, you know what that sentence is worth.

Low and High come from the standard library like everything else — imp WriteLn, Low, High : std; — because nothing in Mica is ambient. That is the import doctrine doing its usual work: the top of the file lists everything the program can say.

A matrix is one shape with two ranges

Grid = array[1..2, -1..1] of int64;
for row := 1 to 2 do
    for column := -1 to 1 do
        g[row, column] := row * 10 + column;
  row 1: 9 10 11
  row 2: 19 20 21

Grid is not an array of arrays. It is one type with two index ranges, each with its own bounds, selected by one expression g[row, column]. There is no inner array to alias, no row to accidentally share, and no jagged case — every row exists and has the same columns, by construction.

The copy rule reaches all the way down

Path nests everything this page has: a string, a count, and three Points inside a named array type. One assignment copies the whole of it:

copied := walk;
walk.pts[0].x := 99;
walk.name := "detour";
  shore still holds pts[0].x = 10 after detour changed its own to 99

However deep the nesting goes, it is one value. There is no depth at which Mica silently switches from copying to sharing — no “shallow copy”, no reference at level three. The value-and-pointer article proved this for a flat record; here it holds through record-in-array-in-record, because there was never a special case to fall out of.

Passing and returning obey the same rule at every size:

function Ramp(seed : int64) : Row;

Ramp builds a five-element array in its own frame and returns it by value — the caller owns the elements from the assignment on, and no pointer into a dead frame is involved. Building shapes in helper functions is ordinary Mica, not an idiom to avoid.

An index is checked twice

Here is the ending the whole page walks toward. Take Row, whose indices run −2 to 2, and write one index too far — as a constant:

WriteLn("%lld", r[3]);
analyzer error 5128: constant index expression at position 1 in selection
operation 'index' has value '3' outside array index domain
'subrange[-2..2] of int32'

The program never compiled. The bounds are part of the type, the index is a constant, so the collision is a compile-time fact and the compiler states it.

Now hide the 3 in a variable, where no compiler can know it early:

i := 3;
WriteLn("%lld", r[i]);
Mica runtime failure: reason=index_out_of_range (12)
Mica runtime context: file=B2.mica, line=12, column=22
Mica runtime source:     WriteLn("%lld", r[i]);

The program compiled, ran, and trapped at the exact line, naming the file, the position, and quoting the source. What it did not do is read r[3] — the neighbouring memory that a C program would have quietly returned, and that three decades of CVEs are made of.

That is the whole policy in one pair: what can be known early is refused early; what cannot is checked at the moment of truth, and the failure names its line. You will meet the same shape everywhere in Mica — format strings checked at compile time, conversions that fail loudly at run time — and the release tiers do not turn the check off.

Try it. Both runs above are one-line edits to the example. Make them, and note which one your editor flags before you even build — the language server reports 5128 the moment you save the file.

What this does not do

No dynamic sizing. Every array on this page has bounds fixed in the type. Growing at run time is a different shape with its own article — dynamic arrays, next in this section.

No jagged rows. A Grid is rectangular by construction. Rows of differing lengths are a dynamic-array composition, deliberately visible when you build one.

A copy costs its size, at every size. copied := walk copies the string descriptor, the count and three Points because that is what you wrote. When a shape is big enough that copying hurts, the pointer from the previous article is the explicit tool.

Bounds checks are not free, and not removable. An indexed access pays its comparison unless the optimizer can prove the index in range — the counted for over Low..High is exactly the shape it proves. There is no build flag that trades the check away.

What the compiler proved

Every shape on this page existed in one declaration, every copy went as deep as the value, a whole array crossed a call boundary and came back owned, and both wrong indices were caught — one before the program existed, one at the line the trap named.

Next

Dynamic arrays — the shape that grows: array of T, Append, length against capacity, and what the descriptor actually copies.