Foreign function interface (FFI)
mu can call native libraries via dlopen, dlsym, and dlcall, or use the
higher-level ffi.fn helper for constant bindings.
Example
import "ffi"
puts := ffi.fn("/usr/lib/libSystem.B.dylib", "puts", "i32", ["cstr"])
fflush := ffi.fn("/usr/lib/libSystem.B.dylib", "fflush", "i32", ["ptr"])
puts("hello from mu")
fflush(nil)
The fflush is not decoration. puts writes into libc’s own buffered stdout,
and mu exits through the raw exit syscall without running libc’s atexit
handlers — so without the flush the program prints nothing at all and looks
broken. Anything that writes through libc’s stdio needs the same treatment.
Where FFI works
Raw syscall works on every supported backend/target. Calling C libraries is a
separate FFI layer with explicit platform limits:
| Execution path | C FFI status |
|---|---|
| Bytecode VM (Go host) | darwin and linux when the host is built with cgo; otherwise the FFI builtins return ffi not supported on this platform |
| Native build (Go backend) | supported on the Go native targets; tests cover darwin/arm64, linux/amd64, and linux/arm64 |
| Self-hosted VM | supported through the VM’s FFI bridge where the host FFI builtins are available |
| Self-hosted native | supported on darwin/arm64 and linux/amd64; linux/riscv64 currently omits the direct FFI builtins |
ffi.fn is the convenient path for stable bindings: the library path, symbol,
return type and argument types must be compile-time constants so native builds
can lower the call. The raw dlopen / dlsym / dlcall builtins are still
available for dynamic calls where the execution path supports them.
Notes
- Signatures are explicit (
i8,i16,i32,i64,u8,u16,u32,u64,isize,usize,ptr,cstr,void). cstris valid for arguments only, andvoidis valid for returns only.- Errors are returned as values you can inspect — a missing library or symbol
gives you an
ERROR, not a crash, so you can bind optimistically and fall back.
See docs/Specification.md for full FFI rules.
Next steps
- Start with the language tour for syntax basics.
- Read the runtime guide for VM/native constraints.
- Explore the standard library modules that build on FFI:
sqlitebinds a real C library, andobjcreaches the whole Objective-C runtime — including the float and struct methods the integer-only signatures above cannot express, viaNSInvocation— which is what theuitoolkit’s macOS backend is built on.