Take a two-field record and one innocent line:

b := a;

Now answer, in the language you use most: if a.x changes tomorrow, does b.x change with it?

In most mainstream languages the answer is it depends on what a is — a number no, an object yes, and for anything in between you had better know which kind the library author chose. Whole categories of bugs live in that table: the list two functions accidentally share, the “copy” that wasn’t, the mutation that arrives from a module you never imported.

Mica’s answer is no. Not “no for scalars” — no. An assignment copies what you see, two names never share storage unless one of them says pointer, and you cannot write the sharing without writing the word.

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

make -C examples/ValueAndPointer run

Assignment copies the value

a.x := 3;
a.y := 4;
b := a;
a.x := 99;
WriteLn("  a.x is now %lld, and b.x is still %lld", a.x, b.x);
  a.x is now 99, and b.x is still 3

b := a copied both fields, and from that moment the records are strangers. There is no shared backing object behind them, because there is no backing object at all — a record is its bytes, laid out where the variable lives.

Arrays follow the same rule:

t := s;
s[2] := -1;
  s[2] is now -1, and t[2] is still 20

Notice the two things that did not happen: the array did not decay to a pointer, and the assignment did not alias two names to one array object. One rule for every type, which is why the proof fits in two lines.

A parameter is a value

The same rule decides what a call can do to you:

procedure Grow(v : Point);
begin
    v.x := v.x + 100;
    WriteLn("  inside Grow, the copy's x: %lld", v.x);
end;
Grow(a);
WriteLn("  a.x after Grow returned: %lld", a.x);
  inside Grow, the copy's x: 199
  a.x after Grow returned: 99

v is Grow’s own record — born as a copy at the call, dead at the return. The mutation inside is real and the caller never sees it. Passing a value can never be the moment two routines start sharing state, which is why you can read a call like Grow(a) and know, from the call site alone, that a is the same after it as before.

That guarantee is doing quiet work all over the language. It is why a task’s arguments are snapshots: schedule Worker(cell) copies like every other call, and no scheduling delay can turn the copy into a window. Concurrency did not need a new rule — it inherited this one.

The pointer, written down

Sharing storage is sometimes exactly what you want. Mica does not forbid it; it makes you write it:

var
    n : int64;
    p : pointer int64;
n := 7;
p := address n;
value p := value p + 10;
  n after 'value p := value p + 10': 17

Three spellings carry the whole mechanism. pointer int64 declares the arrow’s type. address n — the only way an arrow comes to exist — takes one. And value p is the cell behind it, on the reading side and the writing side alike.

The dereference is never implicit on a scalar. That sounds like ceremony until the day you audit a function: every line that can touch shared storage contains the word value, so the search box answers a question that elsewhere requires a data-flow analysis.

A pointer is a value too

What does the one assignment rule say about pointers themselves?

q := p;
value q := 50;
WriteLn("  n through p, after a write through q: %lld", value p);
  n through p, after a write through q: 50

q := p copies the pointer — the arrow, not the target. Now one cell stands behind two arrows, and a write through either is visible through the other. This is real sharing, and it is exactly as wide as the two pointer declarations that admit it. WYSIWYG: what you see marked is what is shared, nothing else is.

The explicit out-parameter

Put pointer in a parameter and you have the honest version of the pattern every language needs — a routine that changes its caller’s variable:

procedure Bump(cell : pointer int64);
begin
    value cell := value cell + 1;
end;
Bump(address n);
  n after Bump(address n): 51

Both ends of the agreement are written down. The declaration says pointer, so the callee admits what it does; the call site says address, so a reader scanning the caller sees, on that line, that n can change there. This is the idiom the standard library’s ReadLn and Val families use for their results — which is why a conversion that fills your variable is always spelled Val("1234", address count) and never hides the write.

Record fields reach through a pointer

One convenience, precisely bounded:

pp := address a;
pp.y := 41;
  a.y after 'pp.y := 41': 41
  pp.x reads the pointee's x: 99

A field selection through a record pointer needs no valuepp.y is unambiguous, so the language does not make you decorate it. The value spelling belongs to scalars, where a bare p would be ambiguous between the arrow and the cell.

What this does not do

A pointer to a local cannot outlive the local. Try to smuggle one out:

function Leak() : pointer int64;
var
    local : int64;
begin
    local := 5;
    Leak := address local;
end;
analyzer error 5150: cannot return pointer to local variable 'local': only
heap-origin pointers, pointer parameters, or lexical parent-scope variables
are allowed

The frame dies at the return; an arrow into it would dangle, so the program is refused. The full story of which pointers may travel how far — owning pointers, borrows, and the alias form — is the ownership article of this series, coming with the memory section.

Copies cost what they cost. A 24-byte record copies as 24 bytes; a large array assignment is a large copy, visible in the source exactly where it happens. Mica will not secretly share to save you the copy — if the copy is too expensive, the pointer is the tool, and it is spelled.

No pointer arithmetic. An arrow points at the cell it was taken from. Walking memory is what arrays are for, with bounds the compiler can reason about.

address is not free of rules. Inside a concurrent block, taking a bare address of shared state is refused outright — the arrow would outlive the lock that guards each marked touch. That refusal and its legal replacement are in data-race freedom.

What the compiler proved

Every copy in this program was visible, every share was spelled, and the one attempt to make an arrow outlive its cell did not compile. You can now read any Mica assignment, call, or address and answer the question this article opened with — from the line itself, not from a table of type kinds.

Next

Records and arrays in full — the shapes values come in, nesting, and what passing and returning whole aggregates costs and guarantees. (In preparation; its example is next in the repository.)