moonbit-c-binding

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MoonBit C Binding Guide

MoonBit C绑定指南

Step-by-step workflow for binding any C library to MoonBit using native FFI.
使用原生FFI将任意C库绑定到MoonBit的分步工作流。

When To Use

使用场景

Use this skill when:
  • Adding
    extern "c" fn
    declarations for a C library
  • Writing C stub files (
    moonbit.h
    ,
    MOONBIT_FFI_EXPORT
    )
  • Configuring
    moon.pkg
    for native builds (
    native-stub
    ,
    link.native
    )
  • Choosing
    #borrow
    vs ownership transfer for FFI parameters
  • Wrapping C handles with external objects and finalizers
  • Implementing callback trampolines (closures or
    FuncRef
    )
  • Converting strings between MoonBit (UTF-16) and C (UTF-8)
  • Running AddressSanitizer to catch memory bugs in bindings
在以下场景使用本技能:
  • 为C库添加
    extern "c" fn
    声明
  • 编写C存根文件(
    moonbit.h
    MOONBIT_FFI_EXPORT
  • 为原生构建配置
    moon.pkg
    native-stub
    link.native
  • 为FFI参数选择
    #borrow
    与所有权转移方式
  • 使用外部对象和终结器包装C句柄
  • 实现回调跳板(闭包或
    FuncRef
  • 在MoonBit(UTF-16)与C(UTF-8)之间转换字符串
  • 运行AddressSanitizer以捕获绑定中的内存错误

Plan Mode Instructions

规划模式说明

If you are in plan mode and creating a plan for moonbit-c-binding work:
  1. In your final plan file, include an explicit instruction at the beginning that tells the executing agent to read the moonbit-c-binding skill before starting implementation
  2. The instruction should look like this:
    **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.
  3. This ensures the executing agent has access to all the critical patterns and workflows documented in this skill
若处于规划模式并为moonbit-c-binding工作创建规划:
  1. 在最终规划文件开头添加明确指令,告知执行Agent在开始实现前先阅读moonbit-c-binding技能
  2. 指令格式如下:
    **开始实现前:** 使用Skill工具加载moonbit-c-binding技能,该技能提供了关于FFI声明、所有权注解、C存根和AddressSanitizer验证的全面指导。
  3. 这能确保执行Agent可访问本技能中记录的所有关键模式与工作流

Type Mapping

类型映射

Map C types to MoonBit types before writing any declarations.
C TypeMoonBit TypeNotes
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
在编写任何声明前,先将C类型映射到MoonBit类型。
C类型MoonBit类型说明
int
,
int32_t
Int
32位有符号整数
uint32_t
UInt
32位无符号整数
int64_t
Int64
64位有符号整数
uint64_t
UInt64
64位无符号整数
float
Float
32位浮点数
double
Double
64位浮点数
bool
Bool
在C ABI中以
int32_t
传递(而非C99的
_Bool
uint8_t
,
char
Byte
单字节
void
Unit
仅作为返回类型
void *
(不透明,GC管理)
type Handle
(不透明)
带终结器的外部对象
void *
(不透明,C管理)
#external
注解的
type Handle
无GC跟踪;由C管理生命周期
const uint8_t *
,
uint8_t *
Bytes
FixedArray[Byte]
若C不存储引用则使用
#borrow
const char *
(UTF-8字符串)
Bytes
运行时自动添加空终止符;可直接传递给C
struct *
(小型,无需清理)
struct Foo(Bytes)
值转Bytes模式
struct *
(需要清理)
type Foo
(不透明)
带终结器的外部对象
int
(枚举/标志)
UInt
,
Int
或常量
enum
enum Foo { A = 0; B = 1; C = 10 }
对应
int32_t
回调函数指针
FuncRef[...]
或闭包
参见@references/callbacks.md
输出参数
int *
Ref[Int]
借用Ref

Workflow

工作流

Follow these 4 phases in order.
按以下4个阶段依次执行。

Phase 1: Project Setup

阶段1:项目设置

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:
json
{
  "preferred-target": "native"
}
Package configuration (
moon.pkg
):
moonbit
options(
  "native-stub": ["stub.c"],
  targets: {
    "ffi.mbt": ["native"]
  },
)
Key fields:
FieldPurpose
"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
:
Avoid
supported-targets: ["native"]
. It prevents downstream packages from building on other targets. Use
targets
to gate individual files instead.
Warning —
cc
/
cc-flags
portability:
Setting
cc
disables TCC for debug builds. Setting
cc-flags
with
-I
/
-L
breaks 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.
配置
moon.mod.json
moon.pkg
以支持原生编译。
模块配置(
moon.mod.json
):
添加
"preferred-target": "native"
,使
moon build
moon test
moon run
默认使用原生后端:
json
{
  "preferred-target": "native"
}
包配置(
moon.pkg
):
moonbit
options(
  "native-stub": ["stub.c"],
  targets: {
    "ffi.mbt": ["native"]
  },
)
关键字段:
字段用途
"native-stub"
待编译的C源文件。必须与
moon.pkg
在同一目录下。
targets
限制
.mbt
文件的后端:
"ffi.mbt": ["native"]
link(native("cc-flags": ...))
编译标志(
-I
-D
)。仅适用于系统库。
link(native("cc-link-flags": ...))
链接器标志(
-L
-l
)。仅适用于系统库。
link(native("stub-cc-flags": ...))
仅针对存根文件的编译标志
link(native(exports: ...))
将MoonBit函数导出到C(反向调用)
警告 —
supported-targets
避免使用
supported-targets: ["native"]
。这会阻止下游包在其他目标平台构建。请使用
targets
限制单个文件。
警告 —
cc
/
cc-flags
可移植性:
设置
cc
会禁用调试构建的TCC。设置带
-I
/
-L
cc-flags
会破坏Windows平台兼容性。仅针对系统库设置这些选项。
包含库源码:
"native-stub"
中的所有文件必须与
moon.pkg
在同一目录下。关于包含策略(扁平化、仅头文件、系统库链接),请参见@references/including-c-sources.md。

Phase 2: FFI Layer

阶段2:FFI层

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):
mbt
// 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"
c
// 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:
mbt
///|
#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:
AnnotationWhen 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:
  • Annotate every non-primitive parameter as
    #borrow
    or
    #owned
    .
  • Primitives (
    Int
    ,
    UInt
    ,
    Bool
    ,
    Double
    , etc.) are passed by value — no annotation needed.
  • If unsure whether C stores a reference, do NOT use
    #borrow
    .
  • Use
    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
// 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
}
mbt
// 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):
c
MOONBIT_FFI_EXPORT
void *moonbit_settings_new(void) {
  return moonbit_make_bytes(sizeof(settings_t), 0);
}
mbt
///|
struct Settings(Bytes)  // backed by GC-managed Bytes, no finalizer
moonbit.h
core API:
APIPurpose
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
).
同时编写外部声明和C存根。保持外部声明私有;在阶段3中暴露安全包装器。
extern "c"
extern "C"
均有效——选择一种大小写并保持一致(例如,若同时面向JS目标则匹配
extern "js"
)。
外部对象模式(带清理的C句柄,GC管理):
mbt
// ffi.mbt(在targets中限制为native)

///|
type Parser  // 基于外部对象的不透明类型

///|
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"
c
// 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);
  // 不要释放ptr —— GC管理容器
}

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
注解模式
(C指针,C管理生命周期):
当C完全管理指针的生命周期(无需GC清理)时,为类型添加
#external
注解。指针将作为原始
void*
传递,无需引用计数:
mbt
///|
#external
type RawPtr  // void*,不被GC跟踪

///|
extern "c" fn raw_create() -> RawPtr = "lib_create"

///|
extern "c" fn raw_destroy(ptr : RawPtr) = "lib_destroy"
#external
是一种注解(类似
#borrow
#owned
)——它位于
type
声明的单独行上,而非同一行。
无需C存根包装器或
moonbit_make_external_object
——MoonBit外部声明直接调用C函数。当C API有显式的创建/销毁函数且你希望手动控制生命周期时使用此模式。
所有权注解:
注解使用场景
#borrow(param)
C仅在调用期间读取,不存储引用
#owned(param)
所有权转移给C;C必须在使用完毕后调用
moonbit_decref
规则:
  • 每个非原始类型参数都必须标注
    #borrow
    #owned
  • 原始类型(
    Int
    UInt
    Bool
    Double
    等)按值传递——无需注解。
  • 若不确定C是否存储引用,请不要使用
    #borrow
  • 对于C会写回值的输出参数,结合
    #borrow
    使用
    Ref[T]
关于详细的所有权语义,请参见@references/ownership-and-memory.md。
跨FFI的字符串转换:
MoonBit的
Bytes
由运行时自动添加空终止符,因此可直接传递给期望
const char *
的C函数。对于反向转换(C字符串到MoonBit),使用
moonbit_make_bytes
+
memcpy
c
// C端:将C字符串作为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;  // 若str是malloc分配的,返回前需free(str)
}
mbt
// MoonBit端:将UTF-8 Bytes解码为String
// 需要在moon.pkg中导入"moonbitlang/core/encoding/utf8"
///|
pub fn get_name(handle : Handle) -> String {
  @utf8.decode_lossy(get_name_ffi(handle))
}
值转Bytes模式(小型结构体,无需清理):
c
MOONBIT_FFI_EXPORT
void *moonbit_settings_new(void) {
  return moonbit_make_bytes(sizeof(settings_t), 0);
}
mbt
///|
struct Settings(Bytes)  // 基于GC管理的Bytes,无终结器
moonbit.h
核心API:
API用途
moonbit_make_external_object(finalizer, size)
带清理终结器的GC跟踪对象
moonbit_make_bytes(len, init)
GC管理的字节数组(MoonBit的
Bytes
moonbit_incref(ptr)
防止GC回收C持有的对象
moonbit_decref(ptr)
释放C的引用(与incref配对使用)
Moonbit_array_length(arr)
GC管理的数组或Bytes的长度
MOONBIT_FFI_EXPORT
所有导出函数必需的宏
完整API请阅读
$MOON_HOME/lib/moonbit.h
(默认
MOON_HOME
~/.moon
)。

Phase 3: MoonBit API

阶段3:MoonBit API

Build safe public wrappers over the raw externs.
Type declarations:
mbt
///|
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:
mbt
///|
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:
mbt
///|
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.
在原始外部声明之上构建安全的公共包装器。
类型声明:
mbt
///|
type Parser          // 不透明类型,基于外部对象(带终结器)

///|
struct Settings(Bytes)  // 值类型,基于GC管理的Bytes

///|
struct Node(Bytes)      // 小型值结构体
安全构造函数与方法:
mbt
///|
pub fn Parser::new() -> Parser {
  ts_parser_new()
}

///|
pub fn Parser::set_language(self : Parser, language : Language) -> Bool {
  ts_parser_set_language(self, language)
}
错误映射:
mbt
///|
pub fn result_from_status(status : Int) -> Unit raise {
  if status < 0 {
    raise MyLibError(status)
  }
}
关于回调模式(FuncRef、闭包、跳板),请参见@references/callbacks.md。

Phase 4: Testing

阶段4:测试

bash
moon test --target native -v
Run with AddressSanitizer to catch memory bugs:
bash
python3 scripts/run-asan.py \
  --repo-root <project-root> \
  --pkg moon.pkg \
  --pkg main/moon.pkg
See @references/asan-validation.md for details.
bash
moon test --target native -v
运行AddressSanitizer以捕获内存错误:
bash
python3 scripts/run-asan.py \
  --repo-root <project-root> \
  --pkg moon.pkg \
  --pkg main/moon.pkg
详情请参见@references/asan-validation.md。

Decision Table

决策表

SituationPatternKey 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 GCExternal 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 cleanupValue-as-Bytes
moonbit_make_bytes
+
struct Foo(Bytes)
C returns null on failureNullable wrapperCheck null, return
Option
or raise error
Callback with data parameterFuncRef + Callback trickSee @references/callbacks.md
Callback without data parameterFuncRef onlySee @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
场景模式关键操作
C仅在调用期间读取指针
#borrow(param)
C端无需decref
C获取指针所有权
#owned(param)
C必须调用
moonbit_decref
C句柄需要在GC时清理外部对象+终结器使用
moonbit_make_external_object
C指针,由C管理生命周期
type
上添加
#external
注解
无GC跟踪;显式调用C的destroy函数
小型C结构体,无需清理值转Bytes使用
moonbit_make_bytes
+
struct Foo(Bytes)
C返回null表示失败可空包装器检查null,返回
Option
或抛出错误
带数据参数的回调FuncRef + Callback技巧参见@references/callbacks.md
无数据参数的回调仅使用FuncRef参见@references/callbacks.md
C字符串(UTF-8)输出跨FFI使用
Bytes
C端使用
moonbit_make_bytes
+
memcpy
;MoonBit端使用
@utf8.decode_lossy
输出参数(
int *result
#borrow
Ref[T]
C写入Ref,MoonBit读取
.val

Common Pitfalls

常见陷阱

  1. 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.
  2. Forgetting
    moonbit_decref
    on owned parameters.
    Every non-borrowed, non-primitive parameter transfers ownership to C. Missing decrefs leak memory.
  3. Calling
    free()
    on external object containers.
    The GC manages the container. Finalizers must only release the inner C resource.
  4. Using
    moonbit_make_bytes
    for structs with inner pointers.
    Bytes have no finalizer, so inner heap allocations leak. Use external objects instead.
  5. 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.
  6. Forgetting the
    MOONBIT_FFI_EXPORT
    macro.
    Without it, the function is invisible to the MoonBit linker.
  1. 当C存储指针时使用
    #borrow
    :GC可能在C持有过期引用时回收对象。仅在调用范围内访问时使用borrow。
  2. 忘记对owned参数调用
    moonbit_decref
    :每个非borrow的非原始类型参数都会将所有权转移给C。遗漏decref会导致内存泄漏。
  3. 对外部对象容器调用
    free()
    :容器由GC管理。终结器仅需释放内部的C资源。
  4. 对带内部指针的结构体使用
    moonbit_make_bytes
    :Bytes无终结器,因此内部堆分配会泄漏。请改用外部对象。
  5. 回调调用前忘记
    moonbit_incref
    :当C回调MoonBit时,GC可能运行。调用前对MoonBit管理的对象执行incref;调用后执行decref。
  6. 遗漏
    MOONBIT_FFI_EXPORT
    :没有该宏,函数对MoonBit链接器不可见。

References

参考资料

@references/ownership-and-memory.md @references/callbacks.md @references/including-c-sources.md @references/asan-validation.md
@references/ownership-and-memory.md @references/callbacks.md @references/including-c-sources.md @references/asan-validation.md