Mica has no function pointers. A routine is called with its argument list, never read by name, and that is a design decision the whole language leans on: every call graph is static, every capture is visible, and nothing on a page can secretly run something the page does not show.

The C world’s libraries, meanwhile, hand out their events through exactly the thing Mica refuses. sqlite3_exec wants a function pointer to fire once per row. qsort wants one to compare. Sooner or later a real program meets one of these, and the question is how to serve it without bending the law.

The answer is the third declared subprogram kind, beside task and generator: the callback.

The kind

type
    Tally = record
        rows : int64;
    end;

callback CountRow(context : pointer Tally, columns : int32, values : pointer int64, names : pointer int64) : int32;
begin
    context.rows := context.rows + 1;
    CountRow := 0;
end;

A callback is a routine with a C-compatible shape — a return value or none, exactly as the C side declares it. What makes it a kind of its own is the law attached to the word:

  • A foreign library performs every firing. Mica never calls a callback. Writing CountRow(...) anywhere is a compile error that says so.
  • Its name stands in exactly one place: as the argument at a callback slot — a parameter an imported function’s contract declares for it. As a value, an operand, or an ordinary argument, the name is refused.
  • Its whole environment is explicit. A callback body reads its parameters and the unit’s own root storage — nothing else. The state it works on lives in the context record whose address your call passes through the library’s userdata parameter, and the library hands that pointer back to every firing, untouched. Reaching into an enclosing routine’s frame is refused with a sentence that names the fix: move the state into the context record.

That last rule is what keeps the capture visible. When you read

rc := SqliteExecRaw(db, "select reading from m", CountRow, address tally, address sink);

everything the callback can touch is on that line: CountRow is the code, address tally is the state. There is no hidden environment to wonder about, which is the same WYSIWYG the value model promises everywhere else.

The slot

The contract side declares which parameter is the slot and what shape belongs in it — the class stating when the library fires the callback, the C signature a bound callback must match, and which sibling parameter carries the userdata. The contracts reference has the exact form.

The class matters. A during_call slot fires only while the imported call itself runs — sqlite’s row callback, qsort’s comparison — so your frame provably outlives every firing. A registered slot stores the callback and fires it after the call returns, possibly from a thread the library owns; that class is declared today and refused at every binding, until the road that serves it arrives on the task model’s own terms.

You will rarely write one

The callback is the boundary tool, not the daily idiom. A curated library wraps the callback-shaped C road in the shapes Mica already reads well — the sqlite library’s daily road is Query, the binds, for row in Rows(q), the column readers — and the callback stays for the places where the C shape is the honest one.

The sqlite library is the kind’s first user: the pull road for every day, the callback road on sqlite3_exec when you want it.