en un clic
moonbit-c-binding
// Guide for writing MoonBit bindings to C libraries using native FFI. Use when adding extern "c" declarations, writing C stubs with moonbit.h, configuring native-stub and link.native in moon.pkg, choosing
// Guide for writing MoonBit bindings to C libraries using native FFI. Use when adding extern "c" declarations, writing C stubs with moonbit.h, configuring native-stub and link.native in moon.pkg, choosing
Persist mid-session progress (plan refinements, task progress notes, scope changes, decisions, blockers) to the bit issues created by start-work. Use as a checkpoint before context compaction, after meaningful progress (multiple tasks done, plan refined, scope shifted), before pausing work, or when the user mentions 'save session', 'snapshot', 'checkpoint', 'update bit issue', or 'persist progress'.
Restore session state (plan, target files, in-flight tasks) from bit issues created by the start-work skill. Use when resuming work after Claude Code restart, when the in-memory task list is empty but bit issues exist for the current branch, or when the user mentions "restore session", "repair session", "pick up where I left off", or references a prior bit-issue-tracked session.
Guide for writing, refactoring, and testing MoonBit projects. Use when working in MoonBit modules or packages, organizing MoonBit files, using moon tooling (build/check/run/test/doc/ide etc.), or following MoonBit-specific layout, documentation, and testing conventions.
Use when writing or refactoring proof-carrying code in MoonBit, especially for Why3-backed specifications, abstraction functions, representation invariants, proof assertions, recursive verified data structures, or reducing trusted proof bridges.
Refactor MoonBit code to be idiomatic: shrink public APIs, convert functions to methods, use pattern matching with views, add loop invariants, and ensure test coverage without regressions. Use when updating MoonBit packages or refactoring MoonBit APIs, modules, or tests.
Start implementation work with worktree isolation and cross-session file conflict avoidance. Use this skill when beginning any non-trivial code change: after plan mode, when creating a new branch, implementing features, fixing bugs across multiple files, or refactoring. Also use when the user mentions worktree, bit issue, session coordination, or parallel work.
| name | moonbit-c-binding |
| description | Guide for writing MoonBit bindings to C libraries using native FFI. Use when adding extern "c" declarations, writing C stubs with moonbit.h, configuring native-stub and link.native in moon.pkg, choosing |
Step-by-step workflow for binding any C library to MoonBit using native FFI.
Use this skill when:
extern "c" fn declarations for a C librarymoonbit.h, MOONBIT_FFI_EXPORT)moon.pkg for native builds (native-stub, link.native)#borrow vs ownership transfer for FFI parametersFuncRef)If you are in plan mode and creating a plan for moonbit-c-binding work:
**Before starting implementation:** Use the Skill tool to load the moonbit-c-binding skill, which provides comprehensive guidance on FFI declarations, ownership annotations, C stubs, and AddressSanitizer validation.
Map C types to MoonBit types before writing any declarations.
| C Type | MoonBit Type | Notes |
|---|---|---|
int, int32_t | Int | 32-bit signed |
uint32_t | UInt | 32-bit unsigned |
int64_t | Int64 | 64-bit signed |
uint64_t | UInt64 | 64-bit unsigned |
float | Float | 32-bit float |
double | Double | 64-bit float |
bool | Bool | Passed as int32_t in the C ABI (not C99 _Bool) |
uint8_t, char | Byte | Single byte |
void | Unit | Return type only |
void * (opaque, GC-managed) | type Handle (opaque) | External object with finalizer |
void * (opaque, C-managed) | type Handle with #external annotation | No GC tracking; C manages lifetime |
const uint8_t *, uint8_t * | Bytes or FixedArray[Byte] | Use #borrow if C doesn't store it |
const char * (UTF-8 string) | Bytes | Null-terminated by runtime; pass directly to C |
struct * (small, no cleanup) | struct Foo(Bytes) | Value-as-Bytes pattern |
struct * (needs cleanup) | type Foo (opaque) | External object with finalizer |
int (enum/flags) | UInt, Int, or constant enum | enum Foo { A = 0; B = 1; C = 10 } maps to int32_t |
| callback function pointer | FuncRef[...] or closure | See @references/callbacks.md |
output int * | Ref[Int] | Borrow the Ref |
Follow these 4 phases in order.
Set up moon.mod.json and moon.pkg for native compilation.
Module configuration (moon.mod.json): Add "preferred-target": "native" so that moon build, moon test, and moon run default to the native backend:
{
"preferred-target": "native"
}
Package configuration (moon.pkg):
options(
"native-stub": ["stub.c"],
targets: {
"ffi.mbt": ["native"]
},
)
Key fields:
| Field | Purpose |
|---|---|
"native-stub" | C source files to compile. Must be in the same directory as moon.pkg. |
targets | Gate .mbt files to backends: "ffi.mbt": ["native"] |
link(native("cc-flags": ...)) | Compile flags (-I, -D). Only for system libraries. |
link(native("cc-link-flags": ...)) | Linker flags (-L, -l). Only for system libraries. |
link(native("stub-cc-flags": ...)) | Compile flags for stub files only |
link(native(exports: ...)) | Export MoonBit functions to C (reverse direction) |
Warning —
supported-targets: Avoidsupported-targets: ["native"]. It prevents downstream packages from building on other targets. Usetargetsto gate individual files instead.
Warning —
cc/cc-flagsportability: Settingccdisables TCC for debug builds. Settingcc-flagswith-I/-Lbreaks Windows portability. Only set these for system libraries.
Including library sources: All files in "native-stub" must be in the same directory as moon.pkg. For inclusion strategies (flattening, header-only, system library linking), see @references/including-c-sources.md.
Write extern declarations and C stubs together. Keep externs private; expose safe wrappers in Phase 3. Both extern "c" and extern "C" are valid — choose one casing and be consistent (e.g., match extern "js" if also targeting JS).
External object pattern (C handle with cleanup, GC-managed):
// ffi.mbt (gated to native in targets)
///|
type Parser // opaque type backed by external object
///|
extern "c" fn ts_parser_new() -> Parser = "moonbit_ts_parser_new"
///|
#borrow(parser)
extern "c" fn ts_parser_language(parser : Parser) -> Language = "moonbit_ts_parser_language"
// stub.c
#include "tree_sitter/api.h"
#include <moonbit.h>
typedef struct { TSParser *parser; } MoonBitTSParser;
static void moonbit_ts_parser_destroy(void *ptr) {
ts_parser_delete(((MoonBitTSParser *)ptr)->parser);
// Do NOT free ptr -- GC manages the container
}
MOONBIT_FFI_EXPORT
MoonBitTSParser *moonbit_ts_parser_new(void) {
MoonBitTSParser *p = (MoonBitTSParser *)moonbit_make_external_object(
moonbit_ts_parser_destroy, sizeof(TSParser *)
);
p->parser = ts_parser_new();
return p;
}
#external annotation pattern (C pointer, C-managed lifetime):
When C fully manages the pointer's lifetime (no GC cleanup needed), annotate the type with #external. The pointer is passed as raw void* without reference counting:
///|
#external
type RawPtr // void*, not GC-tracked
///|
extern "c" fn raw_create() -> RawPtr = "lib_create"
///|
extern "c" fn raw_destroy(ptr : RawPtr) = "lib_destroy"
#external is an annotation (like #borrow and #owned) — it goes on its own line before the type declaration, not on the same line.
No C stub wrapper or moonbit_make_external_object is needed — the MoonBit extern calls the C function directly. Use this when the C API has explicit create/destroy functions and you want manual lifetime control.
Ownership annotations:
| Annotation | When to use |
|---|---|
#borrow(param) | C only reads during the call, does not store a reference |
#owned(param) | Ownership transfers to C; C must moonbit_decref when done |
Rules:
#borrow or #owned.Int, UInt, Bool, Double, etc.) are passed by value — no annotation needed.#borrow.Ref[T] with #borrow for output parameters where C writes a value back.For detailed ownership semantics, see @references/ownership-and-memory.md.
String conversion across FFI:
MoonBit Bytes is null-terminated by the runtime, so it can be passed directly to C functions expecting const char *. For the reverse direction (C string to MoonBit), use moonbit_make_bytes + memcpy:
// C side: return a C string as MoonBit Bytes
MOONBIT_FFI_EXPORT
moonbit_bytes_t moonbit_get_name(void *handle) {
const char *str = lib_get_name(handle);
int32_t len = strlen(str);
moonbit_bytes_t bytes = moonbit_make_bytes(len, 0);
memcpy(bytes, str, len);
return bytes; // if str was malloc'd, free(str) before returning
}
// MoonBit side: decode UTF-8 Bytes to String
// Requires import "moonbitlang/core/encoding/utf8" in moon.pkg
///|
pub fn get_name(handle : Handle) -> String {
@utf8.decode_lossy(get_name_ffi(handle))
}
Value-as-Bytes pattern (small struct, no cleanup):
MOONBIT_FFI_EXPORT
void *moonbit_settings_new(void) {
return moonbit_make_bytes(sizeof(settings_t), 0);
}
///|
struct Settings(Bytes) // backed by GC-managed Bytes, no finalizer
moonbit.h core API:
| API | Purpose |
|---|---|
moonbit_make_external_object(finalizer, size) | GC-tracked object with cleanup finalizer |
moonbit_make_bytes(len, init) | GC-managed byte array (MoonBit Bytes) |
moonbit_incref(ptr) | Prevent GC collection of C-held object |
moonbit_decref(ptr) | Release C's reference (pair with incref) |
Moonbit_array_length(arr) | Length of GC-managed array or Bytes |
MOONBIT_FFI_EXPORT | Required macro on all exported functions |
For the full API, read $MOON_HOME/lib/moonbit.h (default MOON_HOME is ~/.moon).
Build safe public wrappers over the raw externs.
Type declarations:
///|
type Parser // opaque, backed by external object (has finalizer)
///|
struct Settings(Bytes) // value type, backed by GC-managed Bytes
///|
struct Node(Bytes) // small value struct
Safe constructors and methods:
///|
pub fn Parser::new() -> Parser {
ts_parser_new()
}
///|
pub fn Parser::set_language(self : Parser, language : Language) -> Bool {
ts_parser_set_language(self, language)
}
Error mapping:
///|
pub fn result_from_status(status : Int) -> Unit raise {
if status < 0 {
raise MyLibError(status)
}
}
For callback patterns (FuncRef, closures, trampolines), see @references/callbacks.md.
moon test --target native -v
Run with AddressSanitizer to catch memory bugs:
python3 scripts/run-asan.py \
--repo-root <project-root> \
--pkg moon.pkg \
--pkg main/moon.pkg
See @references/asan-validation.md for details.
| Situation | Pattern | Key Action |
|---|---|---|
| C reads pointer only during call | #borrow(param) | No decref in C |
| C takes ownership of pointer | #owned(param) | C must moonbit_decref |
| C handle needs cleanup on GC | External object + finalizer | moonbit_make_external_object |
| C pointer, C manages lifetime | #external annotation on type | No GC tracking; call C destroy explicitly |
| Small C struct, no cleanup | Value-as-Bytes | moonbit_make_bytes + struct Foo(Bytes) |
| C returns null on failure | Nullable wrapper | Check null, return Option or raise error |
| Callback with data parameter | FuncRef + Callback trick | See @references/callbacks.md |
| Callback without data parameter | FuncRef only | See @references/callbacks.md |
| C string (UTF-8) output | Bytes across FFI | moonbit_make_bytes + memcpy in C; @utf8.decode_lossy in MoonBit |
Output parameter (int *result) | Ref[T] with #borrow | C writes into Ref, MoonBit reads .val |
Using #borrow when C stores the pointer. The GC may collect the object while C holds a stale reference. Only borrow for call-scoped access.
Forgetting moonbit_decref on owned parameters. Every non-borrowed, non-primitive parameter transfers ownership to C. Missing decrefs leak memory.
Calling free() on external object containers. The GC manages the container. Finalizers must only release the inner C resource.
Using moonbit_make_bytes for structs with inner pointers. Bytes have no finalizer, so inner heap allocations leak. Use external objects instead.
Missing moonbit_incref before callback invocation. When C calls back into MoonBit, the GC may run. Incref MoonBit-managed objects before the call; decref afterward.
Forgetting the MOONBIT_FFI_EXPORT macro. Without it, the function is invisible to the MoonBit linker.
@references/ownership-and-memory.md @references/callbacks.md @references/including-c-sources.md @references/asan-validation.md