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 runThis 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] = 6The 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 21Grid 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 99However 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.