This is part 2 of the series. Part 1 got you a working container and a program
you could step through in a debugger; this part reads that program properly —
what its four parts are, why every name has to be imported, and what the
compiler already knows about your WriteLn before it builds anything.
The example is examples/Hello
in the tutorial repository. Keep that one file open: everything below happens
in it, and the last section asks you to change it.
Getting the file
If you followed part 1 you pasted your own Hello.mica. From here on the
series works from the repository, so that every example, its build, and its
debug configuration are already wired up:
git clone https://gitlab.com/mica-lang/mica-container.git
code mica-containerVS Code offers Reopen in Container in the corner — click it. When the window reloads you are inside the same image as part 1, with the Mica extension active, and the examples are all there. Then, in a terminal inside the container:
make -C examples/Hello runmake prints the compiler’s own progress — the platform it targeted, the
standard library it resolved, the link line — and then runs the program:
Hello — the shape of a Mica program
The greeting
text: Hello, Mica!
runes: 12
The answer
6 * 7 = 42The example, walked
Four parts, and nothing else
program Hello;
imp
WriteLn, Length : std;
var
greeting : string;
runes : uint64;
answer : int64;
begin
...
end.Every Mica program is exactly this: program names it, imp names what it
uses, var declares its variables with their types written down, and the block
between begin and the final end. is what runs. There is no arrangement of
these to learn — the order is fixed, and a program that needs no variables
simply has no var section.
program Hello; does one thing worth knowing: it names the binary. The
compiler writes build/Hello/Hello because of that line, not because of the
file name. The convention that the file is also called Hello.mica exists so
the two never disagree.
imp means nothing is ambient
imp
WriteLn, Length : std;WriteLn is not a keyword and not a built-in that is simply there. It is a
name from the unit std, and it reaches this program because this line asks
for it. So does Length. That rule has no exceptions in Mica — not for the
standard library, not for a library of your own, not for a C function — which
means the first lines of any Mica source are an honest and complete list of
what that file depends on.
The practical consequence arrives the first time you use a name you did not import:
analyzer error 5018: identifier not found: LengthNothing is silently in scope, so nothing can silently change under you when a library adds a name.
Types are written down
var
greeting : string;
runes : uint64;
answer : int64;string holds text as Unicode runes rather than bytes. int64 is the 64-bit
signed integer — what a whole number is when nothing narrower is asked for.
And runes is a uint64 for a reason worth pausing on: it holds the result of
Length, a count, and a count cannot be negative. The type says so.
Assignment, and the operator Mica does not have to guess about
greeting := "Hello, Mica!";
runes := Length(greeting);
answer := 6 * 7;:= assigns and = compares. They are different operators, so an assignment
written where a comparison was meant cannot quietly compile into the wrong
program — the two spellings cannot be mistaken for each other.
Length counts runes, not bytes. "Hello, Mica!" is twelve characters, and
it is twelve whether the program was compiled for the utf-32 or the utf-8
encoding. A Mica string measures itself in the units a reader would count.
The format string is checked at compile time
WriteLn(" text: %ls", greeting);
WriteLn(" runes: %llu", runes);%ls takes a string; %llu takes a uint64. A format string here is not a
runtime convention: the compiler checks it against the actual arguments, and
a mismatch is a build failure. The
star forms (%.*ls, %*lld) are refused outright: the star reads a width from
an argument the call does not count, so it could only ever print garbage —
spell the width as a literal, or print the whole value. The
next section makes you cause one.
Try it
1. Rename the program. Change the first line to program Greeter; and
rebuild:
make -C examples/Hello clean && make -C examples/Hello runThe build now produces build/Hello/Greeter — the folder still follows the
file, the binary follows the program declaration. Change it back.
2. Break the format string on purpose. Change %llu to %lld on the
runes line and rebuild. The compiler refuses, and names exactly what is
wrong:
analyzer error 5184: format string validation error: format specifier '%lld' at position 1
incompatible with type: uint64%lld is the signed 64-bit specifier and runes is unsigned. This is the
whole point of the checking: not a warning, not a garbled line of output at
2 a.m., but a refusal to build with the position and the type spelled out. If
you have the Mica extension installed, you saw the same message underlined in
the editor before you ever ran make — the language server is the compiler.
Change it back.
3. Count something else. Replace the greeting with text of your own —
try "Hello, 世界 🚀" — and look at the rune count:
text: Hello, 世界 🚀
runes: 11Eleven, not the seventeen bytes that text occupies in utf-8. Every character counts once, whatever it costs to store.
What the compiler proved
Before this program ran, the compiler had already established that every name it uses was imported, that every variable had a declared type, and that every format specifier matched the type of the argument beside it. None of those are conventions or lint rules — a program that violates one does not build.
That is the trade Mica makes everywhere: say what you mean once, in the source, and the compiler holds you to it rather than leaving it for a test to discover.
Next
Part 3: Types that change, and what it costs — when a value
moves to a wider type on its own, when you have to say as, and what a
narrowing cast actually does to a number that does not fit.