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
- functions/extern.md — basic extern usage
- memory/pointers.md — pointer operators, region policy, pointer arithmetic gate, NULL failsafe, and C ABI table
- types/string.md — string ABI
- types/int.md — LBIM types