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-container

VS 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 run

make 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 = 42

The 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: Length

Nothing 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 run

The 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: 11

Eleven, 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.