← Back to AILP Home

Advanced FFI Patterns

Large Struct Passing (LBIM)

Types ≥128 bits are passed via sret/byval pointers, not registers:

// int128, int256, etc. are passed by pointer at ABI level
extern func:big_math = int128(int128:a, int128:b);
// C side receives: i128* sret, i128* byval, i128* byval

String Return from Extern

C functions that return strings must return NitpickString:

// C side
typedef struct { char* data; int64_t length; } NitpickString;

NitpickString my_string_func(const char* input) {
    NitpickString result;
    result.data = strdup(input);
    result.length = strlen(input);
    return result;
}

Handle Pattern

Use int64 or ?* for opaque resources whose lifetime is owned by a C library. This is the most stable ABI for contexts, callbacks, and double-pointer-style APIs that allocate into an out parameter:

extern func:create_context = int64();        // returns handle as int64
extern func:destroy_context = NIL(int64:h);  // takes handle as int64

When a C API wants T **out, prefer a shim that returns the created pointer as int64/?* or fills a single erased buffer argument. The T->-> surface is documented in the pointer chapter, but nested-pointer parser polish remains a carry-forward audit item, so public FFI wrappers should keep out-parameters opaque.

Nitpick function values are fat closure pairs, not raw C callback pointers. For C callbacks, pass an opaque int64/?* token into a C trampoline rather than declaring the callback as a Nitpick function value in an extern signature.

Store Before Pass

When passing extern function results directly to pass, store in a variable first:

// WRONG: pass(extern_func());
// RIGHT:
int32:val = extern_func();
pass val;

Stdio Buffering Mismatch (POLISH-013)

Nitpick's print() and println() call the write() syscall directly — they are unbuffered. C's printf, fputs, and puts write to a buffered stream (FILE* stdout). When mixing both in the same program (e.g., a C shim alongside Nitpick I/O), the C output may be delayed or lost on program exit.

Symptom: C shim output appears out of order or missing when interleaved with Nitpick println() calls.

Fix: Call fflush(stdout) at the end of any C function that writes to stdout:

// C shim
void my_shim_print(const char* msg) {
    puts(msg);
    fflush(stdout);  // flush C buffer to fd 1 before Nitpick read/write
}

Alternatively, use write(1, buf, len) in C shims for fully unbuffered output matching Nitpick's model:

#include <unistd.h>
void my_shim_print(const char* msg, int64_t len) {
    write(1, msg, (size_t)len);
}

Related