Every language claims it can call C. The differences are in what you must write to get it, and what you must trust once you have: a foreign-block dialect with a binding generator, a comment language with a scheduler toll on every call, untyped call stanzas that are wrong silently. Mica speaks the C ABI through one JSON file — and after that, a C function is an imported name like any other, type-checked at every call site, costing a plain native call.
The example is MicaCallsC — deliberately split across several Mica files, one per kind of value crossing the boundary, so it doubles as the reference for “how does a value of this shape travel?”
The contract
A C library is not imported by including its header. Its surface is
declared in an interop contract — mica.external, beside the sources —
which states the link line and each function’s exact signature:
{
"name": "micacallsc",
"language": "c",
"contract": {
"link": { "static": { "flags": ["../build/MicaCallsC/libmicacallsc.a"] } },
"functions": [
{
"language_name": "ShiftScalarPacket",
"external_name_utf8": "mica_shift_scalar_packet",
"parameters": [
{"name": "valueIn", "type": {"kind": "named", "name": "ScalarPacket"},
"passing_mode": "call_by_value"}
],
"return_type": {"kind": "named", "name": "ScalarPacket"}
}
]
}
}The full format is a published JSON Schema —
reference/library_contracts.schema.json
in the tutorial repository, copied verbatim from the compiler’s own loader.
From the calling source there is nothing to see:
imp
ShiftScalarPacket, ScalarPacket : micacallsc;A contract can also be earned instead of written: when the library is
itself Mica, --emit-contract generates it, and the
curated POSIX/Linux surfaces are contracts embedded in the compiler —
the next tutorial’s subject.
Checked at the call, like everything else
The contract makes the C function’s type the compiler’s knowledge, so a wrong call is refused where you wrote it, not discovered in a core dump:
analyzer error 5180: incompatible parameter and argument data types at
position 1 in call to DwScale: expected float64, got stringThat single message is most of the safety argument: the traditional C FFI failure mode — a silently wrong prototype — cannot exist, because there is no second prototype. One declaration, one truth, both languages held to it.
What crosses, and how
The example sends every shape across, section by section, and prints what C received and returned:
== Scalar packet contract surface ==
input text / glyph = packet-start / A
shifted i32 / u32 = -2994 / 3006
shifted text / glyph = packet-touched / Z
shifted flag = 1
const score = 1158
== Packed aggregate contract surface ==
payload = 5, 16, 4.25
payload score = 25.25
lane = (21,30) (22,40)
lane total = 70
== Text contract surface ==
row = zero | | two
wrapper = north | west / center / east | southThree passing modes carry everything, and they are Mica’s own vocabulary
unchanged: by value (C gets a copy), by pointer (address at the
call, C may write through), by const pointer (C may read, the compiler
holds it to that). A thirteen-field record with floats, bools, a unicode
glyph and a string crosses by value in both directions; packed records
and packed arrays share one C ABI story; UTF-32 strings move with no
adapters — the note lines in the output are the example’s own summary of
each section’s lesson.
What this does not do
- No variadic C functions through a hand-written contract’s ordinary
road — the curated
cstdsurface owns the printf family, where the compiler can check format strings. - No C macros, no inline functions. A contract declares linkable symbols; a macro is not a symbol. Wrap it in a real function on the C side.
- The contract is trusted. If it declares a signature the C library does not actually have, the call follows the declaration — the same trust every FFI extends, here at least written in one reviewable place with a schema behind it.
Try it
git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/MicaCallsC runChange one parameter’s type in mica.external and rebuild: every affected
Mica call site refuses with 5180-class messages. The contract is not
documentation — it is the type system’s evidence.
Next
C calls Mica — the same boundary from the other side, where the C program is in charge and no Mica source is in sight.