Scripting & runtime · native FFI
Call explicitly typed C ABI functions.
The FFI opens a native library, resolves a symbol, and calls it through a signature you provide. It is an unsafe typed boundary—not automatic C header or ABI inference.
lib := ffi_open("./libmath_helpers.so")
sum := ffi_call(lib, "add_i64", "i64(i64,i64)", 20, 22)
print(sum)
ffi_close(lib) Use .so on Linux, .dylib on macOS, and .dll on Windows. Keep the library open for every call and callback that depends on it; calling through a closed handle is an error.
Supported boundary
Signatures cover the maintained scalar integer, floating-point, boolean, C-string, pointer and void forms, plus explicit null pointers. Nift-owned native buffers and declared struct layouts provide bounded memory exchange without pretending arbitrary C layouts are self-describing. Unsupported signatures, layouts, calling conventions, argument counts, conversions, or missing symbols fail explicitly.
Callbacks and lifetime
Callbacks are bounded synchronous calls from native code into a Nift callable. Keep the callback and its owning runtime alive for the entire native call. Native code must not retain the callback for later or invoke it asynchronously after ffi_call returns. Callback failures cross the boundary as controlled runtime errors rather than exceptions escaping through C.
A correct signature can still point at unsafe native behavior. Validate lengths and ownership, match the library's documented ABI exactly, and prefer the supported embedding API when you control both sides.
Recoverable loader failures
Two contained loader operations are recoverable and can be caught: failing to load a library (ffi.library_load_failed) and a missing symbol on a valid handle (ffi.symbol_not_found).
try {
lib := ffi_open("./libmath_helpers.so")
} catch(err) {
print(err.code) // ffi.library_load_failed
}
lib := ffi_open("./libmath_helpers.so")
try {
sum := ffi_call(lib, "missing_symbol", "i64(i64,i64)", 1, 2)
} catch(err) {
print(err.code) // ffi.symbol_not_found
} Everything else stays fatal and cannot be caught: invalid signatures/types, wrong argument count, forged or closed handles, callback contract violations, buffer/C-string lifetime misuse, unload failures, double close, native crashes, signals and foreign exceptions. Failed loads register no handle and failed lookups do not mutate the library registry.