Documentation channel: v0.0.5 at compiler commit 2c9c7e8. The latest tagged release is v0.0.5; pages identify APIs that are new in v0.0.5.
FFI & embedding
Strut supports two-way interoperability with native code and with host programs. A Strut program can export a stable C ABI surface and be called from native code, and it can embed foreign native code and call it. The same surface lets external programs (C, C++, Python, Go, Node.js, C#) load and drive compiled Strut logic.
The two-way FFI and the embedding API are new in v0.0.5. The one-way native linking described in Linking & FFI remains available and unchanged.
Two directions
- Strut -> native: call C functions from Strut through
extern "C"declarations inside anunsafeblock (see the linking page). - Native -> Strut: build a shared library from Strut source and call its exported entry points from C, C++, or another language, or load compiled Strut code through the embedding API.
Export a C ABI surface
Mark a function export "C" to place it in the generated shared library with an unmangled C symbol. Build the library with --shared and emit a matching header with --emit-c-header.
strut math.p --shared -o libmath.so --emit-c-header math.h
export "C" function add(int a, int b) -> int {
return a + b;
}
Exported signatures are validated against an ABI-safe type set. Fixed-width integers, string/bytes (as pointer+length with explicit ownership), plain-layout (POD) structs, borrowed primitive ptr<T>/ref<T>, and supported callbacks may cross the boundary. A C++ exception never unwinds into a C caller.
Strings, bytes, and ownership
string and bytes cross the ABI as data pointer plus length. Inputs are borrowed for the duration of the call; outputs are owned by the caller and released with the module's release function. Freeing a null/empty result is safe.
Checked errors across the boundary
An exported function that declares checked errors reports them through a status value plus a module-owned opaque error handle carrying the error type, message, and code. A failed call returns a non-zero status and a concrete error; success returns the value normally.
Callbacks
Two callback forms are supported in both directions:
- Borrowed callbacks (
function<...>as an export parameter) are synchronous and direct-callee only; they must not escape the native call.native_callback<...>declares a native function pointer plus context for native -> Strut calls. - Retained callbacks (
retained_callback<sig>) have a shared identity and an explicit retain/release contract, may be invoked from other threads, and remain callable until their final release. A checked-error signature is preserved.
Embedding API (C)
The embedding surface is an opaque strut_embed_context (#include "strut/embed.h"). A host creates a context, loads Strut source or a file, invokes exported functions by name, converts values, and destroys the context.
#include "strut/embed.h"
strut_embed_context* ctx = strut_embed_context_create();
strut_embed_error* err = NULL;
if (strut_embed_context_load_file(ctx, "app.p", &err) != 0) { /* handle err */ }
strut_embed_value out;
if (strut_embed_invoke(ctx, "add", args, nargs, &out, &err) == 0) { /* use out */ }
strut_embed_value_free(ctx, &out);
strut_embed_context_destroy(ctx);
- Values: bool, int, float, string, bytes, retained values, and typed borrowed callbacks.
- Retained values are single-owner; they hold a module lease while live, survive a successful reload, and must be released before the context is destroyed.
destroyreports BUSY while an invocation or a retained lease is outstanding and succeeds once released.- Reload and destroy during an in-flight invocation are deterministically BUSY; the invocation completes.
- Diagnostics are machine-readable; checked Strut errors keep their type/message/code.
Host consumers
Executable, independently-tested consumers are provided for C and C++, Python (ctypes), Go (cgo), Node.js (N-API, dependency-free), and C# (P/Invoke). Each builds and runs against the shared embedding library. The Go consumer is not part of the macOS certification path.
Nift interoperability
On Linux, Strut interoperates bidirectionally with the Nift C ABI: a Nift engine can call a Strut export (with dynamic argument marshalling, UTF-8 strings, and byte payloads), and a Strut program can drive a Nift engine. This is Linux-local evidence.
Limitations
boolis not a typed exportable ABI type (an embedding value kind BOOL exists).- This parser version does not support hexadecimal integer literals (use decimal bytes).
- Nift scripts do not decode
\uXXXXescapes; embed real UTF-8 bytes. - UTF-8 string lengths across the ABI are byte lengths.
- Interpreter/runtime-hosted consumers (Python/Go/Node/C#) run in ordinary builds; they are not themselves ASan/UBSan-certified. The native C embedding surface is sanitizer-clean.
