Foreign Function Interface (FFI)
Zeus compiles to a native binary and links like C, so it can call a C function with a direct C‑ABI
call — no wrapper library, no per‑function glue. You declare the C function with the @extern
annotation and call it like any Zeus function.
@extern("C", "abs") function cAbs(x: cint): cint;
console.log(cAbs(-42) as i32); // 42The @ annotation system
@extern is one of Zeus’s first‑class annotations — @name(args) markers that either decorate a
declaration or stand alone as a directive:
| Annotation | Kind | Purpose |
|---|---|---|
@extern(...) | decorator on a function/method | bind an external symbol |
@link(...) | standalone directive | link a native library (see Linking Libraries) |
Annotations lead the declaration and their arguments are string literals. Only the two above are recognized today; the mechanism is general so more can be added later.
@extern forms
@extern has three forms, distinguished by their arguments:
@extern("C", "open") function cOpen(path: cstr, flags: cint): cint; // libc symbol `open`@extern("zeus", "malloc") function cMalloc(size: csize): cptr; // runtime symbol `zeus_malloc`@extern("C", "sym")— a direct C‑ABI call to the raw C symbolsym(e.g. libc’sopen). Arguments are passed by value; the return value is used directly.@extern("zeus", "sym")— the same direct C‑ABI call, but to a Zeus runtime symbol; thezeus_prefix is added automatically (@extern("zeus", "malloc")bindszeus_malloc). This is sugar for binding the runtime’s own helpers.@extern("sym")— a single string binds an internal runtime primordial through the fat ABI. This form is used by the standard library’s built‑ins and is rarely needed in application code.
An @extern function has no body — it ends with ;.
C types
C functions speak C types, not Zeus types. Zeus provides a small set of inert C‑ABI primitives that only cross the FFI boundary (they support no arithmetic or indexing):
| Zeus | C meaning | Notes |
|---|---|---|
cint | int | 32‑bit; file descriptors, flags, most return codes, errno |
clong | long / ssize_t / off_t | 64‑bit on macOS/Linux |
csize | size_t | 64‑bit, unsigned |
cptr | void* | a raw, non‑GC pointer / opaque handle |
cstr | char* | a raw C string pointer |
cdouble | double |
Converting between C and Zeus types
C numbers bridge to Zeus numbers with an explicit as cast; the pointer types cptr and cstr are
freely interchangeable with each other but not with numbers:
let fd: i32 = cOpen(pathCStr, 0 as cint) as i32; // cint result -> i32let n: cint = 42 as cint; // i32 literal -> cintlet raw: cptr = someCStr as cptr; // cstr <-> cptr identityMarshalling helpers
Strings and buffers need conversion at the boundary. Zeus exposes a set of ambient primitives (available everywhere, no import) for building bindings:
// strings <-> C stringscStrFromString(s: string): cstr // NUL-terminated copy (GC-managed)cStrToString(p: cstr): string // read a NUL-terminated C stringcBytesToString(p: cptr, len: clong): string // read `len` raw bytes
// raw memory (caller frees)cMalloc(size: csize): cptrcRealloc(p: cptr, size: csize): cptrcFree(p: cptr): void
// read C struct fields / buffers by byte offsetcReadI32(base: cptr, offset: clong): cintcReadI64(base: cptr, offset: clong): clongcReadF64(base: cptr, offset: clong): cdoublecPtrOffset(base: cptr, offset: clong): cptr// ...cReadI8/I16/U32/Ptr and cWriteI32/I64
cErrno(): cint // read errnocIsNull(p: cptr): cint // 1 if the pointer is NULLA complete binding
@extern("C", "puts") function cPuts(s: cstr): cint;
cPuts(cStrFromString("hello from C"));Reading a value back out — here, a string returned by C:
@extern("C", "getenv") function cGetenv(name: cstr): cstr;
function readEnv(name: string): string { let result: cstr = cGetenv(cStrFromString(name)); if (cIsNull(result as cptr) as i32 != 0) { return ""; } return cStrToString(result);}Safety at the boundary
- Transient arguments (a pointer passed only for the duration of a call) are always safe — the collector scans the stack and never moves the data.
- Retained pointers (C stores a Zeus pointer in its own heap) are not tracked by the collector; keep a Zeus‑side reference alive.
- C types are strict by default: arithmetic on them, or passing a
stringwhere acstris expected, is a compile error — exactly the safety you want at an unsafe boundary.
To link against a third‑party library (not libc, which links automatically), see Linking Libraries.