Two small features carry more everyday code than most big ones. The first
closes the gap between declaring a name and giving it meaning. The second
is the answer to a question every Pascal newcomer asks in week one —
where is return? — and the answer is better than the thing it
replaces.
The example is InitAndLeave.
A declaration that starts alive
const
Base = 40;
var
answer : int64 := Base + 2; { unit level: constant expressions }
function FirstDivisor(n : int64) : int64;
var
d : int64 := 2; { local: any visible expression }A local initializer chains anything the position can see — the parameter, constants, earlier locals, a call result. A unit-level initializer must be a constant expression, and the compiler explains why in the refusal:
analyzer error 5231: the initializer of program-level variable 'n' must be
a constant expression: program-level state is materialized before the body
runs, so there is no earlier moment for run-time codeNo reader ever meets d without knowing what it starts as; no init-order
puzzle can exist because there is no moment before the constants.
A declaration wherever a statement is legal
The var section is not pinned to a block’s head. It is legal wherever a
statement is legal, and it declares into the compound statement that
encloses it — a begin-end compound, a repeat-until body, and a
concurrent block all read it the same way:
begin
var scaled : int64 := answer * 2; { statement position: a run-time
expression is fine here }
for i := 1 to 3 do
begin
var fresh : int64 := 100; { this compound's own variable }
fresh := fresh + i; { 101, 102, 103 — fresh each round }
end;
end;Three consequences follow from that one scope rule, and each is visible in the example’s output:
- The home is the enclosing compound, not the subprogram. A variable declared in a loop body belongs to that body: the enclosing code cannot see it, and the same name in two sibling compounds is two variables.
- The initializer’s store runs at the written line, on every entry. A loop body’s initialized variable starts fresh each round — the store re-runs when control passes the declaration — and a statement standing before the declaration in the same compound still reads the zeroed default the storage was born with.
- A run-time initializer is legal in statement position, even in the program body. The constant-expression rule above binds the block-head program declarations, which are materialized before the body runs; a statement-position store runs mid-body, where any visible expression is as sound as any assignment. Reading a variable declared after your own is still refused, in the same words the block-head rule uses.
One var in statement position reads a single declaration group, so the
statement that follows it is never mistaken for a second group.
leave: the activation exit
Mica has no return, and the difference is not spelling. A function’s
result is its own name — assigned like a variable, kept until the
activation ends — and leave ends the activation:
FirstDivisor := n; { the prime's answer: itself }
while d * d <= n do
begin
if n mod d = 0 then
begin
FirstDivisor := d;
leave; { the activation ends HERE }
end;
d := d + 1;
end;Two consequences. Result-setting and exiting are separate acts, so “the
answer unless something better turns up” costs one assignment before the
loop instead of a flag. And bare leave inside a loop still exits the
activation, not the loop — one word, one meaning, with every pending
defer running on the way out, which is why
on fail leave could reuse the whole mechanism.
when puts the condition on the exit itself, which turns the guard clause
into one line with no pyramid above the real body:
procedure Report(n : int64);
begin
leave when n = 0;
WriteLn(" first divisor of %lld is %lld", n, FirstDivisor(n));
end;The guard clause
first divisor of 91 is 7
first divisor of 97 is 97Leaving a loop needs the loop’s name
To exit a loop early, name it — and leave <label> when exits it from
any depth, in one line:
search : for row := 1 to 5 do
for col := 1 to 5 do
begin
tries := tries + 1;
found := row * 10 + col;
leave search when row * col = 12;
end;Leaving a labeled loop from inside
product 12 first at row 3 col 4, after 14 triesFourteen tries instead of twenty-five — and no break/break 2/flag
ceremony. A name that names no enclosing loop is refused with the scope
rule attached:
analyzer error 5227: no enclosing loop is named 'hunt': a loop name can
only be targeted from inside the loop it namesnext is the family’s small sibling — skip to the loop’s next round:
for i := 1 to 10 do
begin
if i mod 2 = 0 then
next;
odds := odds + i;
end;Skipping with next
the odd numbers below eleven sum to 25What this does not do
leavecarries no value. The result name already has one; the exit is an exit, not an expression. That separation is what let the failure channel, cancellation, and defer all ride the same machinery.- Bare
leavenever means “exit the loop”. In a deeply nested body that reads surprisingly at first and exactly once — after that, everyleavein any code means one thing. - A deferred statement cannot
leave. Cleanups run inside the exit sequence; an exit inside the exit is refused — defer’s rules hold here unchanged.
Try it
git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/InitAndLeave runRename the label to hunt in the leave line only and meet 5227. Then
move FirstDivisor := n; below the loop and watch the prime rows change —
the assignment-then-leave shape made visible by breaking it.
Next
With maps, bit records and this pair, the retired language tour’s lessons all have focused homes. The series’ remaining ground: the C boundary, and tooling end to end.