TL;DR — MoonBit 是一门 WASM-first 语言,默认产物极小、冷启动快、类型系统强。适合边缘函数、浏览器插件、AI 插件沙箱等对加载速度敏感的场景;不适合依赖大量第三方库的服务端系统。
阅读时长约 8 分钟 · 适合正在评估 WASM 方案的工程师
版本说明:实测基于
moon 0.1.20260522/moonc v0.9.3+b53c2807d(2026-05-26),正式选型前请用真实代码复测。
30 秒判断:MoonBit 适不适合你?
| 你的场景 | 建议 |
|---|---|
| 小而独立的 WASM 业务逻辑 | 值得看 MoonBit |
| TypeScript 团队的简单 Wasm 工具 | 先看 AssemblyScript;状态复杂时再评估 MoonBit |
| 依赖大量库的服务端系统 | 优先 Rust / Go / Java / TypeScript |
MoonBit 是什么?
由 IDEA 研究院(张宏波团队,BuckleScript/ReScript 核心作者)主导的为 WebAssembly 优化的多范式语言。核心定位:
| 关键词 | 含义 |
|---|---|
| WASM-first | 设计时就优先编译到 WebAssembly,不是事后适配 |
| Minimal runtime | 尽量少把运行时塞进产物,.wasm 天然小 |
| ML 系类型系统 | 代数数据类型、模式匹配、类型推断、穷尽性检查 |
| 多后端 | 同一份逻辑可面向 Wasm GC、传统 Wasm、JS、native |
一句话:MoonBit 解决的是“一段业务逻辑,要安全、很小、很快地跑在沙箱里”。
为什么需要它?
WASM 场景的难点往往不是“能不能跑”,而是“跑起来的成本够不够低”:
- 边缘函数按请求启动,冷启动慢 = 延迟高
- Chrome MV3 Service Worker 频繁被回收,产物越小恢复越快
- AI 插件市场不希望下发大 runtime
- 沙箱里的用户自定义逻辑,类型安全比“写起来最快”更重要
现有选择各有代价:
| 选择 | 优点 | 主要代价 |
|---|---|---|
| Rust | 性能强、生态成熟、WASM 工具链完整 | 学习曲线陡;默认产物不一定小,常要手动调优。 |
| Go | 团队上手容易,服务端生态好 | 标准 Go Wasm 会带较大的 runtime。 |
| TinyGo | 明显缩小 Go Wasm 体积 | 是独立工具链,语言和库支持不是完整 Go。 |
| AssemblyScript | TypeScript 团队容易上手,微型 Wasm 很小 | 不是普通 TS;复杂对象、内存、运行时选择需要额外理解。 |
| MoonBit | 小体积、类型建模强、无所有权负担、多后端 | 生态新,库和社区还在成长 |
MoonBit 的位置:比 Rust 更容易上手,比 Go 更轻,比 AssemblyScript 更适合复杂类型建模。
三分钟跑起来
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
moon new hello && cd hello
moon run cmd/main或者直接打开 在线 Playground。VS Code 用户建议装 MoonBit 官方插件。
四个核心能力
1. 产物极小
MoonBit 的 release 构建和工具链设计都偏向小体积。原因不是“语法更短”,而是:
- Minimal runtime — 不把用不到的运行时带进去
- 整程序优化 — 没用到的函数和分配链路被消除
- Wasm GC 后端 — 复用宿主 GC,不需要自带分配器
- 专用优化管线 — 工具链从设计上就关注产物体积
结论:MoonBit 的体积优势主要体现在小型、独立、可沙箱化的业务逻辑里。(后面有实测数据。)
2. 编译快
不依赖 LLVM,全链路自己实现 IR 与 codegen。增量编译很快,中等项目全量构建秒级。对“写一点、测一点”的插件和规则引擎很重要。
3. 强类型,无所有权心智负担
ADT、模式匹配、Trait、Option / Result——适合表达协议、状态机、规则引擎。类型能力接近 OCaml / Rust,但不需要学生命周期和借用检查。
示例:二叉搜索树插入
enum Tree[T] {
Leaf
Node(Tree[T], T, Tree[T])
}
fn[T : Compare] Tree::insert(self : Tree[T], x : T) -> Tree[T] {
match self {
Leaf => Node(Leaf, x, Leaf)
Node(l, v, r) =>
match x.compare(v) {
0 => self
n if n < 0 => Node(l.insert(x), v, r)
_ => Node(l, v, r.insert(x))
}
}
}要点:Tree[T] 穷举所有形态;match 漏分支会编译失败;Compare 约束表达类型契约;函数体不需要到处写类型注解。
4. 多后端输出
同一份逻辑可产出 .wasm、.js 或 native 目标:
| 后端 / CLI target | 当前定位 | 典型场景 |
|---|---|---|
Wasm GC / wasm-gc | 主推 | 现代浏览器、新版 Node、支持 Wasm GC 的边缘 runtime |
Wasm / wasm | 兼容路径 | 传统线性内存 Wasm、嵌入式 Wasm、无 GC 环境 |
JavaScript / js | 可用 | 前端、Node.js、不方便引入 Wasm 的环境 |
C / native | 可用但仍在演进 | CLI、服务端工具、需要本地可执行文件的场景 |
LLVM / llvm | 实验性 | 后端实验、高性能原生目标探索 |
注意:WASM 与外部世界交互依赖宿主函数,I/O、网络等能力不能默认认为“直接可用”。wasm-gc 和 use-js-builtin-string 对宿主支持有要求,现代浏览器和新版 Node 支持较好,但边缘 runtime、嵌入式 runtime、组件模型宿主仍需单独验证。
实战示例:边缘函数 JSON 校验
模拟一个 Cloudflare Workers / Vercel Edge handler:解析用户信息、校验、返回 Result。
struct SignupReq {
email : String
age : Int
}
enum SignupError {
InvalidEmail
Underage(Int)
MalformedJson(String)
}
fn[T] parse_json(_body : String) -> Result[T, String] {
Err("invalid json")
}
fn validate(req : SignupReq) -> Result[SignupReq, SignupError] {
if !req.email.contains("@") {
Err(InvalidEmail)
} else if req.age < 18 {
Err(Underage(req.age))
} else {
Ok(req)
}
}
pub fn handle_signup(body : String) -> Result[SignupReq, SignupError] {
parse_json(body)
.map_err(fn(e) { MalformedJson(e) })
|> Result::bind(validate)
}要点:SignupError 本身就是 API 文档;Result 强制调用方处理成功和失败;|> 把解析→映射→校验串成清晰管道。
横向对比:怎么选?
| 语言 | WASM 大小 | 启动 | 类型安全 | 学习曲线 |
|---|---|---|---|---|
| Rust | 百字节 / KB 到 MB,取决于 std、分配、glue code 和调优 | 较慢到快,取决于调优 | 强 + 所有权 | 陡 |
| Go | 标准工具链通常 MB 级 | 慢 | 中 | 平 |
| TinyGo | 十几 KB 到几百 KB | 中 | 中 | 平 |
| AssemblyScript | 百字节级到几十 KB | 快 | TS-like | 平 |
| MoonBit | 几 KB 到几十 KB,小函数可百字节级 | 快 | 强(ADT + Trait) | 中 |
快速决策:
- 要成熟生态和极致性能 → Rust
- 要复用 Go 团队经验 → TinyGo / 标准 Go Wasm
- 要TypeScript 风格的小工具 → AssemblyScript
- 要复杂业务状态 + 小体积 + 强类型 + 多后端 → MoonBit
表中体积区间是工程经验值,不是语言常数。Rust 的无分配纯函数可以百字节级;一旦引入
std、分配、wasm-bindgen、serde 或其他 glue code,体积会快速上升。通过no_std + wee_alloc + wasm-opt -Oz等手段仍可压到很小。
为什么不直接用 AssemblyScript?
如果目标是小型、无依赖、偏计算的 Wasm 工具且团队主要写 TypeScript,AssemblyScript 很可能更直接。但它不是”把现有 TS 直接编成 Wasm”——语法接近 TypeScript,工程边界更接近”用 TS 语法写的面向 Wasm 的静态语言”。
关键差异:
- 运行时要自己选 —
stub是极小 bump allocator 不回收;minimal需手动触发 GC;incremental自动化但引入更多开销 - 标准库不是浏览器/Node 的 — DOM、
fetch、npm 包不会因为语法像 TS 就可用 - 对象在线性内存里 —
String/Array不是 JS 对象,跨边界是指针 - JS 互操作有成本 — 传字符串/对象需要 glue code / loader
结论: 简单 Wasm 工具、TS 团队 → AssemblyScript。业务状态复杂、错误分支多、长期维护 → 评估 MoonBit。
本机实测数据
只想快速阅读可跳过本节。结论:微型纯函数里各家都能百字节级;稍真实的字符串工具里 MoonBit 和 AssemblyScript 仍控制在 1 KB 内;TinyGo 十几 KB;标准 Go 因 runtime 体积明显更大。吞吐方面,线性内存路径各家接近,
wasm-gc+ JS string 路径因逐字符宿主调用而明显慢。ABI 说明:各产物的宿主绑定方式不同——MoonBit
wasm-gc使用use-js-builtin-string,Gojs/wasm使用syscall/js,其余主要通过ptr + len读取线性内存。表格主要用于观察产物量级和吞吐档位,不是严格 ABI 对比。AssemblyScript 表中的
stub/minimal是为小体积显式选择的 runtime;默认 runtime 和 GC 行为不同,体积也会变化。
测试 1:最小导出函数 validate_signup(age, email_has_at) -> 0|1|2
不含 JSON、字符串解析或第三方依赖,只看最小可导出函数的产物下限(2026-05-27 实测):
| 工具链 / 产物 | Raw size | 说明 |
|---|---|---|
AssemblyScript 0.28.17 / stub | 84 B | asc --runtime stub -O3z --noAssert |
AssemblyScript 0.28.17 / minimal | 84 B | asc --runtime minimal -O3z --noAssert |
MoonBit moonc v0.9.3 / wasm-gc release | 93 B | moon build --target wasm-gc --release --strip |
MoonBit moonc v0.9.3 / wasm release | 102 B | 传统线性内存 Wasm target |
Rust rustc 1.95.0 / release-small | 138 B | opt-level = "z" + LTO + strip + panic = "abort" |
Rust rustc 1.95.0 / release | 437 B | 普通 cargo build --release |
TinyGo 0.41.1 / WASI | 14,297 B | tinygo build -target=wasi -opt=z -no-debug -panic=trap |
TinyGo 0.41.1 / wasm | 15,312 B | tinygo build -target=wasm -opt=z -no-debug -panic=trap |
Go 1.26.3 / WASI reactor | 1,816,416 B | //go:wasmexport + -buildmode=c-shared |
Go 1.26.3 / js/wasm | 1,924,219 B | 标准 Go 浏览器 Wasm runtime 路径 |
微型 Wasm(<1 KB)压缩参考价值不大。所有产物已用 Node.js 验证通过。
测试 2:Markdown TOC Analyzer
更接近真实工具:扫描 Markdown,识别 ATX 标题,统计数量、最大层级、做 ASCII lowercase checksum。所有产物验证结果:headings=6, max_level=6, checksum=3850。
| 工具链 / 产物 | Raw size | Gzip -9 | Brotli -q 11 | 说明 |
|---|---|---|---|---|
AssemblyScript 0.28.17 / stub | 537 B | 376 B | 324 B | asc --runtime stub -O3z --noAssert |
AssemblyScript 0.28.17 / minimal | 537 B | 379 B | 324 B | asc --runtime minimal -O3z --noAssert |
MoonBit moonc v0.9.3 / wasm-gc release | 681 B | 444 B | 390 B | use-js-builtin-string + moon build --target wasm-gc --release --strip |
MoonBit moonc v0.9.3 / wasm linear memory | 971 B | 674 B | 593 B | ptr + len 输入 + moon build --target wasm --release --strip |
Rust rustc 1.95.0 / release-small | 3,326 B | 1,737 B | 1,534 B | opt-level = "z" + LTO + strip + panic = "abort" |
TinyGo 0.41.1 / WASI | 15,045 B | 6,843 B | 5,941 B | tinygo build -target=wasi -opt=z -no-debug -panic=trap |
TinyGo 0.41.1 / wasm | 16,081 B | 7,054 B | 6,139 B | tinygo build -target=wasm -opt=z -no-debug -panic=trap |
Rust rustc 1.95.0 / release | 21,908 B | 8,808 B | 7,558 B | 普通 cargo build --release |
Go 1.26.3 / WASI reactor | 1,819,505 B | 544,898 B | 421,680 B | //go:wasmexport + -buildmode=c-shared |
Go 1.26.3 / js/wasm | 1,926,413 B | 571,813 B | 440,637 B | 标准 Go 浏览器 Wasm runtime 路径 |
结论:MoonBit 和 AssemblyScript 接近或低于 1 KB,Rust 瘦身后几 KB,TinyGo 十几 KB,标准 Go 受 runtime 主导在 MB 级。
测试 3:Markdown TOC Analyzer warm throughput
同一份 Markdown analyzer 的运行时吞吐:输入重复 16 次(6,352 B),Node.js v24.6.0 先 warmup 150 ms,再取 5 轮、每轮 500 ms 的 median ops/sec。
| 产物 | Median ops/sec | Median us/op | 相对 MoonBit linear |
|---|---|---|---|
Rust release | 128,635 | 7.77 | 1.13x |
Rust release-small | 123,967 | 8.07 | 1.09x |
AssemblyScript minimal | 116,714 | 8.57 | 1.03x |
AssemblyScript stub | 116,644 | 8.57 | 1.03x |
TinyGo wasm | 114,060 | 8.77 | 1.01x |
MoonBit wasm linear memory | 113,471 | 8.81 | 1.00x |
TinyGo wasi | 113,166 | 8.84 | 1.00x |
Go WASI reactor | 44,696 | 22.37 | 0.39x |
Go js/wasm | 25,442 | 39.31 | 0.22x |
MoonBit wasm-gc + JS builtin string | 14,591 | 68.53 | 0.13x |
结论:线性内存路径各家接近(MoonBit ≈ AssemblyScript ≈ TinyGo),Rust 快约 13%。wasm-gc + JS string 路径因逐字符宿主调用而慢 ~7x——吞吐敏感场景应优先用线性内存 ABI。
测试 4:官方 FFT benchmark 复测
MoonBit 官方博客用 Fast Fourier Transform 展示 #valtype 对数值计算的性能价值,并给出了 moonbit-community/benchmark-fft 仓库。该仓库 README 记录的 MoonBit 环境是 v0.6.25;当前本机是 moonc v0.9.3+b53c2807d,原始 MoonBit 代码不能直接编译,因此复测时只做兼容迁移:更新 Add/Sub/Mul trait 方法名,移除旧版 moonbitlang/x / tonyfettes/uv / fs 依赖,计时改用 moonbitlang/core/bench。
命令:python3 bench_runner.py --runs 10 --inputs 18 20 22。输入 18/20/22 分别表示 2^18 / 2^20 / 2^22 个 complex points。
| Program | 2^18 Median | 2^20 Median | 2^22 Median |
|---|---|---|---|
| MoonBit native | 15.790 ms | 64.759 ms | 300.750 ms |
| Go | 15.755 ms | 64.727 ms | 300.923 ms |
| Rust | 24.176 ms | 98.435 ms | 428.383 ms |
| Swift | 37.286 ms | 155.591 ms | 699.491 ms |
结论:MoonBit native 与 Go 同档,比 Rust 快约 30%、比 Swift 快约 57%(2^22)。FFT 考验 native 数值计算和 value type,与 Markdown 测试暴露的宿主 ABI 成本是不同维度。
适合的场景
| 场景 | 为什么适合 | 主要替代方案 |
|---|---|---|
| 边缘函数的计算核心 | 产物小,冷启动快,沙盒安全。 | Rust、JS/TS、AssemblyScript |
| 浏览器插件 / Chrome MV3 Service Worker | MV3 SW 频繁被回收,重新启动时体积和初始化成本很关键。 | Rust、TS |
| 小程序 / 快应用的 WASM 业务逻辑 | 产物大小和冷启动直接影响体验。 | AssemblyScript、C/C++、Rust |
| IoT 嵌入式设备的 WASM 沙箱业务 | 小体积、沙箱隔离,native / LLVM 路径也值得跟踪。 | C/C++、Rust、MicroPython |
| AI 助手的用户自定义插件 / DSL 沙箱 | WASM 天然沙箱,类型安全方便审计,小体积方便下发。 | QuickJS、V8 isolate、用户手写 JS |
| 前端 Monorepo 的共享业务逻辑 | 同一份逻辑可以面向 JS、Wasm、Node、浏览器扩展。 | TypeScript、Rust |
| 游戏规则 / 实时渲染的轻量业务逻辑 | ADT + 模式匹配适合表达规则和状态机。 | Lua、Rust、AssemblyScript |
| WASM 组件模型相关的小组件 | 类型系统贴合组件类型契约,体积小适合分发;WIT / ABI 工具链成熟度需单独验证。 | Rust、C/C++ |
一句话:凡是对 WASM 产物大小、冷启动速度、沙盒安全敏感,且业务逻辑能清晰隔离的场景,都值得评估 MoonBit。
不适合的场景
- 依赖海量第三方库 — 生态不能和 Rust/Go/JS 相比
- 纯服务端 / 不在乎冷启动 — 常驻进程、批处理用成熟技术更稳
- 需要稳定 ABI / FFI — native 与 LLVM 路径还在演进
- 高吞吐字符串 / 字节扫描热路径 — 需要先做性能 POC,并优先验证线性内存或组件模型边界;不要默认使用 JS string 热循环
- 大团队招人 / 长期维护核心系统 — MoonBit 工程师少
- 强调成熟稳定高于一切 — 语言和工具链仍在快速演进
- 修改/分发编译器 — MoonBit Public License 有限制,需先确认条款
决策清单
| 问题 | 答“是”意味着 |
|---|---|
| 逻辑能独立成小模块? | MoonBit 值得看 |
| 产物大小/冷启动影响体验或成本? | MoonBit 值得看 |
| 业务状态和错误分支越来越复杂? | MoonBit 类型系统有帮助 |
| 热路径主要是大文本 / 字节扫描? | 先测 ABI 和宿主边界,不要只看 Wasm 体积 |
| 严重依赖成熟第三方库? | 先别急着上 MoonBit |
| 需要大量招聘和长期稳定生态? | 谨慎评估,可能先观望 |
MoonBit 不是来替代 Rust 或 JavaScript 的。它是为“WASM-first、小体积、快冷启动、强类型沙箱逻辑”准备的专用工具。
附录:为什么 Rust WASM 比 MoonBit 大?
不是”Rust 天生慢”,而是几个默认设计决策叠加的结果:
| 因素 | Rust 默认行为 | MoonBit 对应策略 |
|---|---|---|
| std/runtime | 胖 std 静态链接,println! 就可能引入格式化引擎、分配器链、Unicode、Panic 处理 | minimal runtime + 整程序优化,未用函数被消除 |
| 内存分配器 | 使用 String/Vec/Box 等堆分配路径就需要带分配器进 WASM | Wasm GC 后端复用宿主 GC,不自带分配器 |
| 优化后端 | LLVM 通用后端,内联保守,泛型单态化可能重复生成代码,需手动接 wasm-opt | 自写 IR + 优化管线,专为 WASM 体积特化 |
Rust 能变小,但门槛不低: #![no_std] → opt-level = “z” + LTO + strip → panic = “abort” → wee_alloc → wasm-opt -Oz → 优化 wasm-bindgen。调完可以和 MoonBit 一样小,代价是放弃标准库生态。
Rust:”默认保留完整能力,Wasm 体积需要额外调优”。 MoonBit:”默认极简、默认 WASM 特化、冷启动/体积第一”。 没有对错,看你的场景更需要成熟生态还是小体积。
参考资源
- 官网 · 在线 Playground · 官方文档
- FFI · Method & Trait · 错误处理
- 编译器仓库 / 许可证 · 标准库 · VS Code 插件
- MoonBit value type / FFT benchmark 博客 · 官方 FFT benchmark 仓库
- Go Wasm export · TinyGo · AssemblyScript
本文中的体积和冷启动判断,建议结合官方 benchmark 与真实业务代码复现。