moonbit-c-binding
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMoonBit 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 declarations for a C library
extern "c" fn - Writing C stub files (,
moonbit.h)MOONBIT_FFI_EXPORT - Configuring for native builds (
moon.pkg,native-stub)link.native - Choosing vs ownership transfer for FFI parameters
#borrow - 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:
- 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
- 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. - This ensures the executing agent has access to all the critical patterns and workflows documented in this skill
若处于规划模式并为moonbit-c-binding工作创建规划:
- 在最终规划文件开头添加明确指令,告知执行Agent在开始实现前先阅读moonbit-c-binding技能
- 指令格式如下:
**开始实现前:** 使用Skill工具加载moonbit-c-binding技能,该技能提供了关于FFI声明、所有权注解、C存根和AddressSanitizer验证的全面指导。 - 这能确保执行Agent可访问本技能中记录的所有关键模式与工作流
Type Mapping
类型映射
Map C types to MoonBit types before writing any declarations.
| C Type | MoonBit Type | Notes |
|---|---|---|
| | 32-bit signed |
| | 32-bit unsigned |
| | 64-bit signed |
| | 64-bit unsigned |
| | 32-bit float |
| | 64-bit float |
| | Passed as |
| | Single byte |
| | Return type only |
| | External object with finalizer |
| | No GC tracking; C manages lifetime |
| | Use |
| | Null-terminated by runtime; pass directly to C |
| | Value-as-Bytes pattern |
| | External object with finalizer |
| | |
| callback function pointer | | See @references/callbacks.md |
output | | Borrow the Ref |
在编写任何声明前,先将C类型映射到MoonBit类型。
| C类型 | MoonBit类型 | 说明 |
|---|---|---|
| | 32位有符号整数 |
| | 32位无符号整数 |
| | 64位有符号整数 |
| | 64位无符号整数 |
| | 32位浮点数 |
| | 64位浮点数 |
| | 在C ABI中以 |
| | 单字节 |
| | 仅作为返回类型 |
| | 带终结器的外部对象 |
| 带 | 无GC跟踪;由C管理生命周期 |
| | 若C不存储引用则使用 |
| | 运行时自动添加空终止符;可直接传递给C |
| | 值转Bytes模式 |
| | 带终结器的外部对象 |
| | |
| 回调函数指针 | | 参见@references/callbacks.md |
输出参数 | | 借用Ref |
Workflow
工作流
Follow these 4 phases in order.
按以下4个阶段依次执行。
Phase 1: Project Setup
阶段1:项目设置
Set up and for native compilation.
moon.mod.jsonmoon.pkgModule configuration (): Add so that , , and default to the native backend:
moon.mod.json"preferred-target": "native"moon buildmoon testmoon runjson
{
"preferred-target": "native"
}Package configuration ():
moon.pkgmoonbit
options(
"native-stub": ["stub.c"],
targets: {
"ffi.mbt": ["native"]
},
)Key fields:
| Field | Purpose |
|---|---|
| C source files to compile. Must be in the same directory as |
| Gate |
| Compile flags ( |
| Linker flags ( |
| Compile flags for stub files only |
| Export MoonBit functions to C (reverse direction) |
Warning —: Avoidsupported-targets. It prevents downstream packages from building on other targets. Usesupported-targets: ["native"]to gate individual files instead.targets
Warning —/ccportability: Settingcc-flagsdisables TCC for debug builds. Settingccwithcc-flags/-Ibreaks Windows portability. Only set these for system libraries.-L
Including library sources: All files in must be in the same directory as . For inclusion strategies (flattening, header-only, system library linking), see @references/including-c-sources.md.
"native-stub"moon.pkg配置和以支持原生编译。
moon.mod.jsonmoon.pkg模块配置(): 添加,使、和默认使用原生后端:
moon.mod.json"preferred-target": "native"moon buildmoon testmoon runjson
{
"preferred-target": "native"
}包配置():
moon.pkgmoonbit
options(
"native-stub": ["stub.c"],
targets: {
"ffi.mbt": ["native"]
},
)关键字段:
| 字段 | 用途 |
|---|---|
| 待编译的C源文件。必须与 |
| 限制 |
| 编译标志( |
| 链接器标志( |
| 仅针对存根文件的编译标志 |
| 将MoonBit函数导出到C(反向调用) |
警告 —: 避免使用supported-targets。这会阻止下游包在其他目标平台构建。请使用supported-targets: ["native"]限制单个文件。targets
警告 —/cc可移植性: 设置cc-flags会禁用调试构建的TCC。设置带cc/-I的-L会破坏Windows平台兼容性。仅针对系统库设置这些选项。cc-flags
包含库源码: 中的所有文件必须与在同一目录下。关于包含策略(扁平化、仅头文件、系统库链接),请参见@references/including-c-sources.md。
"native-stub"moon.pkgPhase 2: FFI Layer
阶段2:FFI层
Write extern declarations and C stubs together. Keep externs private; expose safe wrappers in Phase 3. Both and are valid — choose one casing and be consistent (e.g., match if also targeting JS).
extern "c"extern "C"extern "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;
}#externalWhen C fully manages the pointer's lifetime (no GC cleanup needed), annotate the type with . The pointer is passed as raw without reference counting:
#externalvoid*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#borrow#ownedtypeNo C stub wrapper or 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.
moonbit_make_external_objectOwnership annotations:
| Annotation | When to use |
|---|---|
| C only reads during the call, does not store a reference |
| Ownership transfers to C; C must |
Rules:
- Annotate every non-primitive parameter as or
#borrow.#owned - Primitives (,
Int,UInt,Bool, etc.) are passed by value — no annotation needed.Double - If unsure whether C stores a reference, do NOT use .
#borrow - Use with
Ref[T]for output parameters where C writes a value back.#borrow
For detailed ownership semantics, see @references/ownership-and-memory.md.
String conversion across FFI:
MoonBit is null-terminated by the runtime, so it can be passed directly to C functions expecting . For the reverse direction (C string to MoonBit), use + :
Bytesconst char *moonbit_make_bytesmemcpyc
// 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 finalizermoonbit.h| API | Purpose |
|---|---|
| GC-tracked object with cleanup finalizer |
| GC-managed byte array (MoonBit |
| Prevent GC collection of C-held object |
| Release C's reference (pair with incref) |
| Length of GC-managed array or Bytes |
| Required macro on all exported functions |
For the full API, read (default is ).
$MOON_HOME/lib/moonbit.hMOON_HOME~/.moon同时编写外部声明和C存根。保持外部声明私有;在阶段3中暴露安全包装器。和均有效——选择一种大小写并保持一致(例如,若同时面向JS目标则匹配)。
extern "c"extern "C"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完全管理指针的生命周期(无需GC清理)时,为类型添加注解。指针将作为原始传递,无需引用计数:
#externalvoid*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#ownedtype无需C存根包装器或——MoonBit外部声明直接调用C函数。当C API有显式的创建/销毁函数且你希望手动控制生命周期时使用此模式。
moonbit_make_external_object所有权注解:
| 注解 | 使用场景 |
|---|---|
| C仅在调用期间读取,不存储引用 |
| 所有权转移给C;C必须在使用完毕后调用 |
规则:
- 每个非原始类型参数都必须标注或
#borrow。#owned - 原始类型(、
Int、UInt、Bool等)按值传递——无需注解。Double - 若不确定C是否存储引用,请不要使用。
#borrow - 对于C会写回值的输出参数,结合使用
#borrow。Ref[T]
关于详细的所有权语义,请参见@references/ownership-and-memory.md。
跨FFI的字符串转换:
MoonBit的由运行时自动添加空终止符,因此可直接传递给期望的C函数。对于反向转换(C字符串到MoonBit),使用 + :
Bytesconst char *moonbit_make_bytesmemcpyc
// 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 | 用途 |
|---|---|
| 带清理终结器的GC跟踪对象 |
| GC管理的字节数组(MoonBit的 |
| 防止GC回收C持有的对象 |
| 释放C的引用(与incref配对使用) |
| GC管理的数组或Bytes的长度 |
| 所有导出函数必需的宏 |
完整API请阅读(默认为)。
$MOON_HOME/lib/moonbit.hMOON_HOME~/.moonPhase 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 structSafe 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 -vRun with AddressSanitizer to catch memory bugs:
bash
python3 scripts/run-asan.py \
--repo-root <project-root> \
--pkg moon.pkg \
--pkg main/moon.pkgSee @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
决策表
| Situation | Pattern | Key Action |
|---|---|---|
| C reads pointer only during call | | No decref in C |
| C takes ownership of pointer | | C must |
| C handle needs cleanup on GC | External object + finalizer | |
| C pointer, C manages lifetime | | No GC tracking; call C destroy explicitly |
| Small C struct, no cleanup | Value-as-Bytes | |
| C returns null on failure | Nullable wrapper | Check null, return |
| 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 | | |
Output parameter ( | | C writes into Ref, MoonBit reads |
| 场景 | 模式 | 关键操作 |
|---|---|---|
| C仅在调用期间读取指针 | | C端无需decref |
| C获取指针所有权 | | C必须调用 |
| C句柄需要在GC时清理 | 外部对象+终结器 | 使用 |
| C指针,由C管理生命周期 | 在 | 无GC跟踪;显式调用C的destroy函数 |
| 小型C结构体,无需清理 | 值转Bytes | 使用 |
| C返回null表示失败 | 可空包装器 | 检查null,返回 |
| 带数据参数的回调 | FuncRef + Callback技巧 | 参见@references/callbacks.md |
| 无数据参数的回调 | 仅使用FuncRef | 参见@references/callbacks.md |
| C字符串(UTF-8)输出 | 跨FFI使用 | C端使用 |
输出参数( | 带 | C写入Ref,MoonBit读取 |
Common Pitfalls
常见陷阱
-
Usingwhen C stores the pointer. The GC may collect the object while C holds a stale reference. Only borrow for call-scoped access.
#borrow -
Forgettingon owned parameters. Every non-borrowed, non-primitive parameter transfers ownership to C. Missing decrefs leak memory.
moonbit_decref -
Callingon external object containers. The GC manages the container. Finalizers must only release the inner C resource.
free() -
Usingfor structs with inner pointers. Bytes have no finalizer, so inner heap allocations leak. Use external objects instead.
moonbit_make_bytes -
Missingbefore callback invocation. When C calls back into MoonBit, the GC may run. Incref MoonBit-managed objects before the call; decref afterward.
moonbit_incref -
Forgetting themacro. Without it, the function is invisible to the MoonBit linker.
MOONBIT_FFI_EXPORT
-
当C存储指针时使用:GC可能在C持有过期引用时回收对象。仅在调用范围内访问时使用borrow。
#borrow -
忘记对owned参数调用:每个非borrow的非原始类型参数都会将所有权转移给C。遗漏decref会导致内存泄漏。
moonbit_decref -
对外部对象容器调用:容器由GC管理。终结器仅需释放内部的C资源。
free() -
对带内部指针的结构体使用:Bytes无终结器,因此内部堆分配会泄漏。请改用外部对象。
moonbit_make_bytes -
回调调用前忘记:当C回调MoonBit时,GC可能运行。调用前对MoonBit管理的对象执行incref;调用后执行decref。
moonbit_incref -
遗漏宏:没有该宏,函数对MoonBit链接器不可见。
MOONBIT_FFI_EXPORT
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