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 string

That 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 | south

Three 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 cstd surface 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 run

Change 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.