A program outgrows one file the day two ideas stop wanting to share a screen. What happens next separates languages more than most features do: C shares everything through headers that repeat declarations by hand; Java makes the unit a class whether or not a class is the idea; Go makes visibility a spelling convention. Mica’s answer is Pascal’s, sharpened: a compilation unit whose surface is declaredexp marks what escapes, everything else is private — and an import doctrine with one rule and no special cases.

The example is Units: three files, one dependency order, the smallest complete build.

An object unit and its surface

object geometry;

imp
    SquareRoot = Sqrt : cstd;

type
    exp Point = record
        X : float64;
        Y : float64;
    end;

function SquareOf(v : float64) : float64;   { private — no 'exp' }
begin
    SquareOf := v * v;
end;

exp function Distance(a : Point, b : Point) : float64;
begin
    Distance := SquareRoot(SquareOf(b.X - a.X) + SquareOf(b.Y - a.Y));
end;

An object unit is a compilation unit without a program entry: declarations, a surface, and a closing empty compound statement. The surface is Point, Distance, Midpoint — and not SquareOf, however useful it looks. Try to import it and the compiler explains the whole model in one sentence:

analyzer error 5021: no declarations found in namespace 'geometry' for
identifier: SquareOf — a library exports only its 'exp'-marked
declarations, so a name that exists in its source may still be private

A private helper is not a convention; it is unreachable. The unit’s inside stays free to change without a single importer noticing.

One import rule, no special cases

imp
    WriteLn : std;
    Point, Distance, Midpoint : geometry;
    PathLength : statistics;

geometry is a namespace exactly like std or strings — the import doctrine is one rule: everything arrives by imp, whether written by the Mica project years ago or by you, this morning, in the file beside this one. Nothing is ambient; every name in a file traces to a declaration or an import line at the top. The as rename (Sqrt as SquareRoot) is part of the same rule — the importer chooses its local vocabulary.

Surfaces compose: statistics imports Point from geometry and speaks it in its own exported signature, so the program above it receives a type that traveled through two units:

Units — a program grown past one file

From geometry
  home to shop: 5.000000
  midpoint of that walk: (1.500000, 2.000000)

From statistics
  home via shop to park: 11.000000

The build is a list, the order is a discipline

MICA_SOURCE_FILES := geometry.mica statistics.mica Units.mica

The compiler accepts the sources in any order — it resolves the imports itself. The discipline of listing each unit before its importers is for the reader: a build whose list is a valid dependency order can be read top to bottom, each file naming only files already understood. Every multi-unit program on this site keeps that discipline, and none of them needs to.

The mica.project beside the sources states the same list, so the editor’s diagnostics are the build’s — same units, same surfaces, while you type.

The third road: a library, and its emitted contract

Inside one build, units see each other’s surfaces directly. The moment a library is built separately — its own archive, consumed by programs it knows nothing about — the surface must travel as a file. Mica’s rule: the compiler emits it, nobody writes it.

The Utilities library builds with two switches:

MICA_EXTRA_FLAGS := --output-modifier archive --emit-contract

and the build directory then holds the archive and mica.external — the import surface of every unit in it, written by the compiler from the exp marks. A consumer names that contract in its own project file and imports as if the library were a sibling:

{
    "sources": ["Consumer.mica"],
    "contracts": ["../build/Utilities"]
}

The compiler’s gated corpus holds such pairings on every merge, and Utilities’ other consumer — a plain C program — stays in the repository as the boundary’s own proof.

No header to repeat, no binding file to maintain, no drift possible between what a library exports and what its consumers see — the contract is compiler output, regenerated with the archive it describes.

The unit lifecycle: who runs, and when

The closing begin end. block answers differently for the two unit kinds, and the difference is the deployment story:

  • An object unit’s body must be empty — module initialization there is reserved for a future release, and the parser says so: 2038: only an empty compound statement is supported in libraries and objects, reserved for future use (the “libraries” in that sentence are same-build library targets; the separately built kind below has its own rule).
  • A separately built library unit’s body runs — real initialization, executed before the consuming program’s own body, establishing the library’s static state. And inside that block, defer until program registers a program-lifetime finalizer: its statement runs once at process exit, after the program’s body, in last-in-first-out registration order, reading the unit’s statics as the program last left them — not as initialization first set them.

The compiler’s own test corpus pins the whole shape; its output is the lifecycle in four lines:

main start count=10        { the library's body ran first: count is 10 }
main end count=13          { the program mutated the library's static  }
fin-second                 { registered last, runs first — LIFO        }
fin-first count=13         { reads the static as the program LEFT it   }

Acquisition in the body, release at exit, both beside the state they manage — the same shape defer gives an activation, lifted to the library’s whole lifetime.

What this does not do

  • Signature-private types are legal, and they mean something. A unit may export a function whose result type it keeps private; importers hold such a value without naming its parts — the opaque-handle idiom, chosen rather than forbidden.
  • No cyclic imports. Two units that need each other are one idea split wrongly; the dependency order the site’s examples keep visible is the design tool for finding the right split.
  • No transitive re-export. Importing statistics does not hand you geometry — every file’s imports name exactly what that file uses.
  • Object-unit initialization is reserved, not missing. The lifecycle section above is the current whole truth: object bodies stay empty, library bodies run, finalizers drain LIFO at exit.

Try it

git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/Units run

Add imp SquareOf : geometry; to Units.mica and meet refusal 5021. Then mark SquareOf with exp, watch the import succeed — and notice you just made a promise to every future consumer that the helper will keep existing. exp is an API decision; the refusal is the compiler asking whether you meant to make one.

Next

Generics composes with everything here — an exported generic function is a surface too. The series continues with the tour’s re-homed lessons: maps, bit records, and the leave family.