关于 moonbit-practice
面向 AI 的 MoonBit 编码实践指南,提供语法、项目配置、测试和代码导航建议,并针对 MoonBit v0.10.12 的变化整理常见陷阱。
主要功能
- 01MoonBit 语法与类型指导
- 02使用 moon ide 导航代码
- 03项目配置与工作区建议
- 04测试与快照实践
- 05常见语法错误排查
适用场景
- 01生成符合当前语法的 MoonBit 代码
- 02在 MoonBit 项目中查找 API 定义
- 03配置模块、包和工作区
- 04编写测试并更新快照
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mizchi/ skills --skill moonbit-practice安装后在 Claude Code 中输入/moonbit-practice确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdassets/ci-nix.yamlci.yamlmoon.modmoon.pkgpublish-to-npm.yamlreference/configuration.mdffi-native.mdffi.mdide.mdlanguage.mdmbtx.mdnix.mdperformance.mdrefactor.mdskills-marketplace.mdstdlib.mdtesting.md详细介绍
MoonBit 实践指南
AI 生成 MoonBit 代码的最佳实践。
如果你不理解这里的内容,请使用 moonbit-docs skill 搜索官方文档。
已针对 MoonBit v0.10.12 验证(文档快照:2026-09)。MoonBit 仍处于 1.0 之前的阶段,小版本更新也会破坏语法 — 如果这里的某个构造被拒绝, 请先运行
moon fmt(它会自动迁移大多数弃用内容),然后检查moon explain <error-code>。
指南
代码导航:优先使用 moon ide,而非 Read/Grep
在 MoonBit 项目中,优先使用 moon ide 命令,而非 Read 工具或 grep。
# ❌ Avoid: Reading files directly
Read src/parser.mbt
# ✅ Recommended: Find definitions from symbols
moon ide peek-def Parser::parse
moon ide doc 'Parser::*parse*' # glob search over symbols
# ❌ Avoid: Searching with grep
grep -r "fn parse" .
# ✅ Recommended: Semantic search
moon ide find-references parse
moon ide outline src/parser.mbt
原因:
moon ide提供语义搜索(可区分定义和调用位置)- grep 会匹配注释和字符串
moon ide doc能快速显示 API
未安装 moon 时(沙盒、仅供审查的检出目录),本指南所依赖的验证步骤 — moon fmt、moon check、moon test -u、moon ide doc — 均无法使用。此时:
- 将每个快照的
content=""留空,之后由moon test -u填充。 reference/stdlib.md是包概览,并非完整的签名索引。如果你需要的符号不在其中,且无法运行moon ide doc,请优先使用本指南有说明的构造,而非凭模糊记忆选用其他构造,并标记为未经验证。- 在总结中明确说明哪些部分未经验证。
不要手动编写本应由工具生成的值。
其他规则
- 实现前,使用
moon ide doc '<Type>'探索 API。moon doc <SYMBOL>已弃用——它会打印警告并指向moon ide doc - 配置格式:使用 DSL
moon.mod/moon.pkg。 JSON 格式moon.mod.json/moon.pkg.json已弃用,计划移除;moon fmt会将 JSON 迁移为 DSL。示例:assets/moon.mod、assets/moon.pkg。编辑任一文件前,请查看 reference/configuration.md - 可执行包使用
pkgtype(kind: "executable"),而非options("is-main": true)。pkgtype(kind: "foreign_library")取代options(link: true);普通库无需声明 - 一个仓库包含多个模块时 → 使用工作区(
moon.work),而非独立模块。moon work init <dirs>会创建清单;成员共享同一构建上下文,并可在本地相互解析。moon.mod中的路径依赖已弃用,建议改用moon.work。请参阅 reference/configuration.md 中的“Workspace” - 发布过滤规则位于
.moonignore(若不存在则回退到.gitignore),而非moon.mod中已弃用的include/exclude字段 - 以下示例省略了
pub。 下游包或 JS/Wasm 宿主需要访问的任何内容都必须使用pub(pub fn、pub struct、pub suberror、pub impl、pub extend);包内局部项则无需如此。 - 测试文件:
*_test.mbt是黑盒测试(只能访问包的公开 API),*_wbtest.mbt是白盒测试(也能访问私有项)。独立的test "..." { }代码块应放在这两类文件之一中;白盒测试也可内联到源文件中。黑盒测试通过包别名(@mypackage.f())访问所属包,无需额外导入;只有它使用的其他包才需要添加import { ... } for "test"条目 - 可执行程序入口点:
fn main { ... }——不带参数列表,也不带括号。异步程序使用async fn main { ... }。使用@env.args()读取参数;该包需要声明pkgtype(kind: "executable") - 可见性:
pub会公开名称;pub(all)还会公开结构体的字段,或枚举 /suberror的构造器,这是其他包进行模式匹配所需的。普通的pub struct/pub suberror对外部而言是只读的:只有在所属包内才能构造并进行模式匹配 - 本技能中的参考文件:
reference/language.md(类型、trait、模式匹配)、reference/testing.md(测试布局、快照、属性测试、基准测试)、reference/stdlib.md(核心 API)、reference/configuration.md(moon.mod/moon.pkg/ 工作区)、reference/ide.md(moon ide)、reference/ffi.md和reference/ffi-native.md(FFI)、reference/performance.md、reference/refactor.md、reference/nix.md、reference/mbtx.md、reference/skills-marketplace.md。如有冲突,以 SKILL.md 为准。 - 对于单文件
.mbtx脚本(moon run script.mbtx、moonx script.mbtx、通过moon run -使用标准输入、通过moon run -c '...'使用内联源码、import { }前置代码块),请查看 reference/mbtx.md - 在为 skills.mooncakes.io 定义或发布可执行程序前,请查看 reference/skills-marketplace.md;其中介绍了包内的
SKILL.md、Wasm 目标、moonx、宿主能力、策略和发布验证
常见陷阱
- 变量/函数名不要使用大写字母 — 会导致编译错误
mut仅用于重新赋值,不用于修改字段 — 向数组追加元素不需要它- 不需要
return— 最后一条表达式就是返回值 - 方法需要
Type::前缀 - 不支持
++--— 使用i = i + 1或i += 1 - 传播错误不需要
try— 会自动处理(与 Swift 不同) - 没有
await关键字 — 只需使用async fn声明 - 优先使用范围
for,而不是 C 风格写法 —for i in 0..<n {...} - 函数式
for循环的退出值使用nobreak,而不是else(else已弃用) - 旧语法:
function_name!(...)和function_name(...)?已弃用 for { ... }已弃用 — 无限循环请使用for ;; { ... }或while true { ... }- 跨包调用 trait 方法时,
.语法需要pub extend— 单独的impl永远无法让另一个包中的value.f()生效。请显式附加(pub extend Type with Trait::{ f });没有pub时,附加仅限于当前包;完全没有extend时,调用方必须写成Trait::f(value) trait/impl方法需要fn关键字(v0.10.0) —moon fmt会迁移旧代码;请参阅下文“迁移 trait 或 impl”- 使用
extend附加 trait 方法(v0.10.4) — 隐式地将impl附加为方法的方式已弃用;请参阅下文“迁移 trait 或 impl” try?已移除(v0.10.0) — 对于Result,使用Ok(expr) catch { e => Err(e) };遇到错误时要触发 panic,请使用try!- 结构体
fn new(..)构造函数已移除(v0.10.0) — 请使用用户定义的构造函数fn Type::Type(..);从 v0.10.4 起,任何类型都可以定义构造函数,不再仅限于结构体 immut/array已移除(v0.10.9) — 在 v0.9 中已弃用。在moon.pkg中导入"moonbitlang/core/immut/vector"(默认别名为@immut/vector,即moonbitlang/core/之后的路径),并使用@immut/vector.Vector- 不可变容器上的
from_array已弃用(v0.10.4) — 请使用以类型命名的构造函数,例如@immut/vector.Vector([1, 2, 3]) - 视图运算符不接受负索引(v0.10.4) —
xs[-1:]不再表示“从末尾开始” moonbitlang/sys已弃用(v0.10.9) — 请使用moonbitlang/core/env:let argv = @env.args()(元素 0 是程序本身,因此 CLI 参数为argv[1:])@debug.to_repr(x)已弃用(v0.10.9) — 请使用@debug.Repr(x)- 不带
else的guard必须能证明总是匹配(v0.10.9) — 否则会产生 E0087;预期触发 panic 时请写guard! - Or 模式的
with分支需要加括号(v0.10.9) —Some(x) | (None with x = 0) - 容器/自定义类型应使用
debug_inspect取代inspect(v0.9.2) — 容器类型(Array/Map/Set/Option/Result/元组)的Show已弃用;请参阅“快照测试” - 默认构建目标是
wasm(v0.10.9) — 请显式传入--target js/--target native,或在moon.mod中设置preferred_target
AI 常见语法错误
类型参数的位置
///| NG: fn identity[T] is old syntax
fn identity[T](val: T) -> T { val }
///| OK: Type parameter comes right after fn
fn[T] identity(val: T) -> T { val }
raise 语法
///|
/// NG: -> T!Error was removed
fn parse(s: String) -> Int!Error { ... }
///|
/// OK: Use raise keyword
fn parse(s: String) -> Int raise Error { ... }
Int raise 是 Int raise Error 的简写。
async fn 默认会隐式抛出错误;使用 noraise 可强制不抛出错误。
宏调用
///|
/// NG: ! suffix was removed
assert_true!(true)
///|
/// OK
assert_true(true)
迁移 trait 或 impl
同一段代码中总是会同时触发以下两项变更,因此请一次性完成:
- v0.10.0 — 在 trait 方法和
impl中都添加fn。 - v0.10.4 — 为调用方使用点语法(
value.f())调用的每个 trait 方法添加extend声明;否则点调用会依赖已弃用的隐式附加方式。
moon fmt 只会执行步骤 1。步骤 2 需要手动完成。
///| NG: method without fn (deprecated, warns then removed)
trait I {
f(Self) -> Unit
}
impl I for Int with f(_) {}
///| OK: fn keyword on both the trait method and the impl
trait I {
fn f(Self) -> Unit
}
impl I for Int with fn f(_) {}
多态 trait 方法:方法自身的类型参数放在 fn 之后,
impl 自身的类型参数仍放在 impl 之后。
trait Logger {
fn[X : Show] write_object(Self, X) -> Unit
}
impl Logger for StringBuilder with fn write_object(self, x) {
self.write_string(x.to_string())
}
impl[A] Poly for Array[A] with fn[X] f(self, x : X) { ... }
// ^^^ impl type params ^^^ method type params
moon fmt 会自动重写旧的 trait/impl 语法。
使用 extend 附加 trait 方法(v0.10.4)
以前,编写 impl Trait for Type 会悄悄地将 trait 的所有方法附加到
Type,使其可以通过点调用。此做法现已弃用:上游 trait 新增默认方法后,可能会导致现有的
value.f() 调用产生歧义。请改为显式声明附加的方法。
///| NG (deprecated): relying on implicit attachment
impl Show for Point with fn output(self, logger) { ... }
let s = p.output(logger) // implicit attachment, warns under warning 79
///| OK: attach the methods you want callable with dot syntax
impl Show for Point with fn output(self, logger) { ... }
pub extend Point with Show::{ output, to_string }
- 通用形式:
extend Type with Trait::{ method1, method2 }。pub会使附加的方法成为公开方法;不加pub时,这些方法仅在包内可用。 - 使用 trait 默认方法时,
Self会特化为Type。 - 也可以扩展 trait 对象:
extend &Derived with Super::{ method }。 - 不使用
extend时,仍可通过 trait 调用:Trait::method(value, ...)。 - 警告
implicit_impl_as_method(79)会标记此弃用模式;默认关闭。迁移库时请启用它(moon check --warn-list +79)。
结构体、构造函数和方法
已移除的特殊形式 fn new(..) 由普通的用户定义构造函数 fn Type::Type(..) 取代。自 v0.10.4 起,任何类型都可以使用此构造函数,而不仅限于结构体。方法通过 self : Self 接收接收者。
///| Struct with derives
struct Point {
x : Int
y : Int
} derive(Eq, Debug)
///| User-defined constructor (replaces removed `fn new`)
fn Point::Point(x : Int, y : Int) -> Point {
{ x, y } // field-punning struct literal
}
///| Method: receiver `self : Self`, last expression is the return value
fn Point::manhattan(self : Self) -> Int {
self.x.abs() + self.y.abs()
}
///| Call site: a declared constructor is called by the *type name*, not `Type::Type`
let p = Point(1, 2)
///| Constructors are ordinary functions, so they take labelled and optional
/// arguments and may raise:
fn Endpoint::Endpoint(host : String, port? : Int = 80) -> Endpoint {
Host(host, port)
}
let e = Endpoint("example.com", port=443)
如果没有声明构造函数,仍可使用记录字面量 Point::{ x: 1, y: 2 }(或在类型已知时使用简写 { x: 1, y: 2 })来构造结构体。当构造过程需要校验、默认值或更简短的调用形式时,声明 fn Type::Type(..),然后在所有调用处优先使用 Point(1, 2)。
多行文本
let text =
#|line 1
#|line 2
注释和代码块分隔符
///| 是代码块分隔符。/// 注释会附加到后面的 ///| 代码块。
///|
/// This function is foo
fn foo() -> Unit { ... }
///|
/// This function is bar
fn bar() -> Unit { ... }
避免在文件开头连续使用 ///|,因为它们会创建不同的代码块。
快照测试
moon test -u 会自动填充 inspect(val) / debug_inspect(val) 中为空的 content=""。如何选择(v0.9.2+):
- 基本类型 / 字符串 →
inspect(此处使用Show没问题) - 容器类型(
Array/Map/Set/Option/Result/元组)以及自定义类型 →debug_inspect,因为这些容器的Show已弃用,而Debug会提供结构化、带缩进的输出。Show仅用于面向用户的格式化。 - 深度嵌套的结构 →
@json.inspect,其 JSON 输出经格式化后仍清晰易读。需要derive(ToJson)。
请根据被检查表达式的静态类型进行选择,而不是运行时值:即使 Result[Int, _] 中保存的是 Ok(123),它仍是容器,此规则也会传递应用——要对自定义类型(包括嵌套在容器中的自定义类型)进行快照或断言,该类型必须 derive(Debug);若要通过 @debug.assert_eq 比较相等,还需要 derive(Eq)。
test "snapshot" {
inspect("hello", content="") // primitive/string -> inspect
debug_inspect([1, 2, 3], content="") // container -> debug_inspect
}
///| Custom type: derive Debug to snapshot, derive Eq to assert_eq
struct P {
x : Int
} derive(Eq, Debug)
test "custom type" {
debug_inspect(P::{ x: 1 }, content="")
@debug.assert_eq(P::{ x: 1 }, P::{ x: 1 })
}
运行 moon test -u 后:
test "snapshot" {
inspect("hello", content="hello")
debug_inspect([1, 2, 3], content="[1, 2, 3]")
}
请填写 content="",并让 moon test -u 自动填充。切勿手写预期字符串——Debug 的呈现方式是结构化的,与 Show 不同,因此猜测的值会导致测试首次运行就失败。如果无法运行工具链,请将所有 content="" 留空并说明原因;空快照是诚实的待办事项,错误的快照则是虚假的断言。
以下是 Debug 生成的格式。这些示例是为了帮助你阅读已有快照,而不是编写新快照——编写工作仍应交给 -u:
| 被检查的值 | debug_inspect 的呈现结果 |
|---|---|
[1, 2, 3] | [1, 2, 3] |
Point::{ x: 1, y: 2 }(结构体) | { x: 1, y: 2 } |
Shape::Rect(width=3, height=4)(带标签的枚举) | Rect(width=3, height=4) |
Host("example.com", 443)(位置参数枚举) | Host("example.com", 443) |
Err(InvalidEof)(Result + suberror) | Err(InvalidEof) |
较长的值会换行并缩进,这也是应该让 -u 来写入它们的另一个原因。
属性测试(v0.10.9)
QuickCheck 现在位于标准库 moonbitlang/core/quickcheck 中(别名为 @quickcheck)——请勿添加单独的 moonbitlang/quickcheck 依赖。有两个入口:
@quickcheck.check(property)— 失败时引发错误,成功时不输出内容。被测试的类型需要实现Arbitrary + Shrink + Debug。@quickcheck.report(property)— 返回结构化的QuickCheckReport,而不是引发错误;只需要Arbitrary + Shrink。
test "reverse is an involution" {
@quickcheck.check((xs : Array[Int]) => xs.rev().rev() == xs)
}
derive(Arbitrary) 和 derive(Shrink) 会为自定义类型生成实例。可选的标签参数可调整运行:count?、max_size?、max_shrinks?、seed?(用于重现失败)、带有 discard_ratio? 的 filter?,以及通过 @quickcheck.{label, classify, collect} 获取统计信息的 observe?。
文档测试
可用于 .mbt.md 文件或 ///| 内联注释。
| 代码块 | 行为 |
|---|---|
```mbt check | 由 LSP 检查 |
```mbt test | 作为 test {...} 执行 |
```moonbit | 仅显示(不执行) |
示例(内联注释):
///|
/// Increment an integer by 1
/// ```mbt test
/// inspect(incr(41), content="42")
/// ```
pub fn incr(x : Int) -> Int {
x + 1
}
发布前检查清单
发布前运行:
moon fmt # Format code (also migrates deprecated syntax and JSON config)
moon info # Generate type definition files
pkg.generated.mbti 由 moon info 自动生成。不要直接编辑。
探索内置类型方法
moon ide doc StringView # StringView methods
moon ide doc Array # Array methods
moon ide doc Map # Map methods
快速参考
| 主题 | 命令 | 详情 |
|---|---|---|
| 测试 | moon test | https://docs.moonbitlang.com/en/stable/language/tests |
| 更新快照 | moon test -u | 同上 |
| 筛选测试 | moon test --filter 'glob' | 运行指定测试 |
| 基准测试 | moon bench | https://docs.moonbitlang.com/en/stable/language/benchmarks |
| 文档测试 | moon check / moon test | https://docs.moonbitlang.com/en/stable/language/docs |
| 格式化 | moon fmt | 将 moon.mod.json/moon.pkg.json 迁移为 DSL,并为 trait/impl 添加 fn 关键字。陷阱列表中的其他内容均不会处理——try?、fn new、is-main、extend 都需要手动处理 |
| 生成类型 | moon info | - |
| 解释错误代码 | moon explain E0087 | 或使用 moon check --explain |
| 文档参考 | moon ide doc <Type> | moon doc <SYMBOL> 已弃用 |
| 搜索注册表 | moon search <keyword> | 使用 --json 输出机器可读内容 |
| 运行注册表工具 | moonx author/mod/cmd/tool | 替代已弃用的 moon runwasm |
| 证明检查 | moon prove | 无需单独安装 Why3(v0.10.9) |
| 工作区初始化 | moon work init | 参见 reference/configuration.md |
| 添加工作区 | moon work use mod1 mod2 | 将模块添加到工作区 |
| API 使用分析 | moon ide analyze . | 显示公共 API 使用统计 |
moon ide 工具
用于代码导航,比 grep 更准确。详情参见 reference/ide.md。
子命令包括 peek-def、find-references、outline、doc、hover、
rename 和 analyze——没有 goto-definition。
# Show symbol definition (global search by symbol syntax)
moon ide peek-def Parser::read_u32_leb128
# Package outline
moon ide outline .
# Find references
moon ide find-references TranslationUnit
# Peek a definition from a source location (contextual, substring match)
moon ide peek-def Parser -loc src/parse.mbt:46:4
# Show type signature and docs at a location (unlike peek-def, shows inferred type + doc comments)
moon ide hover my_func -loc src/lib.mbt:10:4
# Rename symbol across the project
moon ide rename old_name new_name
# Analyze public API usage
moon ide analyze . # Current package
moon ide analyze internal/* # Glob pattern
函数式 for 循环
尽可能优先使用函数式 for 循环。这样可读性更好,也更容易推理。
// Functional for loop with state
for i = 0, sum = 0; i <= 10 {
continue i + 1, sum + i // Update state
} nobreak {
sum // Value at loop exit (nobreak, not else)
}
// Range for (recommended)
for i in 0..<n { ... }
for i, v in array { ... } // index and value
// Range for carrying extra loop state (v0.10.0)
for x in xs; sum = 0; sum = sum + x { ... }
// Infinite loop (for { } is deprecated)
for ;; { ... }
循环带标签时,其中的 break / continue 也必须使用标签——在带标签循环内直接使用不带标签的语句已弃用。
带标签的块(v0.10.9)
块可以带标签;break label~ value 会携带该值退出块。可用于跳出嵌套控制流,而无需从函数返回。
fn absolute(n : Int) -> Int {
result~: {
if n < 0 {
break result~ -n
}
n
}
}
不带标签的 break 不能跳出带标签的块,而 continue 只能作用于循环。块和循环标签共用一个命名空间——重用外层标签会遮蔽它,并报告 E0036。
迭代器和列表推导式
[for .. => body] 会构建一个数组;在 => 前添加 if <guard> 可进行筛选。
结果类型取决于预期类型——默认是 Array,也可以是
FixedArray、ReadOnlyArray、String、Bytes 或 Json。
let evens = [for i in 0..<100 if i % 2 == 0 => i]
let labelled = [for i, x in ["a", "b", "c"] => "\{i}: \{x}"]
let text : String = [for x in 0..<3 => (x + 'a').unsafe_to_char()]
推导式中不允许使用 return / break / continue。
使用显式的 [| .. |] 字面量构建 Iter(v0.10.4)——依赖预期类型将 [x, ..xs] 转换为 Iter 已弃用,因为这会在不知不觉中改变求值顺序。
let prefix = [| 1, 2, 3 |]
let values = [| ..prefix, 4, 5 |] // ..xs evaluated now, consumed lazily
let squares = [| for x in 1..<=3 => x * x |] // fully lazy
对于带有状态的惰性或无限序列,直接构建 Iter:
let mut p1 = 1
let mut p2 = 0
let fibs : Iter[Int] = Iter::new(fn() {
let next = p1
p1 = p1 + p2
p2 = next
Some(next)
})
let first_six = fibs.take(6).collect()
迭代器只能单次遍历——被 each / fold / collect 消耗后,请向源请求一个新的迭代器。
模板写入 <+(v0.10.0)
buf <+ expr 会向 StringBuilder 追加内容——比 write_string 更简洁,并且适合搭配 $| 插值来构建标记/字符串。buf <? expr 会有条件地写入。
let buf = StringBuilder()
buf <+ "<ul>"
for item in items {
buf <+ $|<li>\{item}</li>
}
buf <+ "</ul>"
buf.to_string()
Bytes 也支持插值:let b : Bytes = b"value=\{x}"(以 UTF-8 编码)。
正则表达式:=~、lexmatch、lexscan
根据需求,从三种结构中选择:
| 需求 | 结构 |
|---|---|
| 进行一次布尔检查 | input =~ re"abc" |
| 多个正则表达式分支返回不同的值 | lexmatch input with longest { ... } |
| 对流进行分词并移动游标 | 对 @lexbuf.{Lexbuf, AsyncLexbuf, StringScanner} 使用 lexscan |
///| Boolean search (first-match, search-based — anchor with ^ / $ if needed)
let matched = s =~ re"abc"
///| Bind the match and the surrounding text
if s =~ (re"[0-9]+" as digits, before~, after~) { ... }
///| Multi-case dispatch over a String / StringView
let kind = lexmatch input with longest {
(re"^[ \t\r\n]+") => Space
(re"^[A-Za-z][A-Za-z0-9_]*" as t) => Ident(t)
_ => Other
}
右侧必须是正则表达式常量表达式(字面量、const,或用 + / | / 括号组合的常量)——不接受运行时字符串。
旧版实验性的字符串片段 lexmatch 模式和 lexmatch? 已移除;lexscan 在 v0.10.9 中成为稳定功能。
字符串常量
const 支持字符串拼接和插值(v0.8.3+):
const Hello : String = "Hello"
const HelloWorld : String = Hello + " world"
const Message : String =
$|========
$|\{HelloWorld}
$|========
错误处理
MoonBit 使用受检错误。详情请参阅 reference/ffi.md。
有三种声明形式——选择最精简且适用的一种:
///| No payload at all (the common case for a single failure mode).
/// This declares an error type `EmptyInput` with one payload-free constructor
/// of the same name — `raise EmptyInput` works directly.
suberror EmptyInput
///| `derive` attaches to the payload-free form too, with no braces in between
suberror NotFound derive(Eq, Debug)
///| One constructor carrying a payload — the constructor repeats the type name
suberror DivError { DivError(String) }
///| Several constructors, like an enum
suberror ParseError {
InvalidEof
InvalidChar(Char)
}
可见性规则与其他情况相同:pub suberror / pub(all) suberror / priv suberror。
对所有可能要快照或打印的错误类型派生 Debug。 下方 catch 形式返回的 Result 是一个容器,因此 debug_inspect 要求两侧都实现 Debug——包括错误:
suberror ParseError {
InvalidEof
InvalidChar(Char)
} derive(Eq, Debug)
///| Declare with raise, auto-propagates
fn parse(s: String) -> Int raise ParseError {
if s.is_empty() { raise ParseError::InvalidEof }
...
}
///| Convert to Result — `try?` was removed (v0.10.0)
let result : Result[Int, ParseError] = Ok(parse(s)) catch { e => Err(e) }
///| Single expression with a fallback: omit `try`
let n = parse(s) catch { _ => 0 }
///| Panic instead of handling
let n = try! parse(s)
///| Full form: `noraise` branch handles the success value
let n = try parse(s) catch {
ParseError::InvalidEof => -1
_ => 0
} noraise {
v => v
}
对该转换结果进行快照——组合情形:
test "parse failure" {
let r : Result[Int, ParseError] = Ok(parse("")) catch { e => Err(e) }
debug_inspect(r, content="") // moon test -u fills in Err(InvalidEof)
}
如果为绑定标注具体的错误类型(Result[Int, ParseError] 而非 Result[Int, Error]),错误一侧就有 Debug 实例,快照也能正常工作。单独的 Error 是一种开放类型,无法为其附加 derive——请将签名收窄为你自己的 suberror,或匹配错误并对相应分支进行快照。
清理:defer 和 errdefer(v0.10.9)
///| defer: always runs on leaving the body (return / break / continue / raise)
fn read_all(path : String) -> String raise {
let f = open(path)
defer f.close()
f.read_to_string()
}
///| errdefer: runs only when the body raises or is cancelled.
/// Use it when the resource is handed to the caller on success.
async fn connect(addr : Addr) -> Tcp {
let sock = make_tcp_socket()
errdefer sock.close()
connect_tcp_socket(sock, addr)
sock
}
连续的 defer 会按相反顺序运行。右侧不允许使用 return / break / continue;清理操作本身可以抛出错误(异步代码中也可以等待),此时其错误会替换原始错误。无法抛出错误的函数体中使用 errdefer 会报告 E0091;只进行清理并重新抛出的全捕获处理应改为 errdefer(E0092)。
Guard(v0.10.9)
guard 会进行穷尽性分析。可能失败的 guard 必须带有 else;如果编译器无法证明它总是成功,就会报告 E0087。当预期终止执行时,请使用 guard!——guard! 不接受 else。
///| Fallible guard: `else` supplies the value for the whole enclosing function
fn guarded_get(array : Array[Int], index : Int) -> Int? {
guard index >= 0 && index < array.length() else { None }
Some(array[index])
}
///| With a pattern, via `is`
fn plain_text(r : Resource) -> String raise Error {
guard r is PlainText(text) else { fail("not plain text") }
text
}
///| Provably total, or a deliberate panic: `guard!` takes no `else`
fn require_some(value : Int?) -> Int {
guard! value is Some(result)
result
}
穷尽条件中多余的 ! 或 else 现在会产生警告。
CI 和发布
第三方操作
优先使用官方 curl 安装程序(assets/ci.yaml)或搭配 moonbit-overlay 的 Nix(assets/ci-nix.yaml),而不是 hustcer/setup-moonbit@v1 等第三方操作。安装程序简短,且不会给操作供应链增加任何依赖;存在 flake.nix 时,Nix 方案完全可复现。
moon update 是必需的
运行器启动时注册表索引为空。任何依赖注册表托管的 MoonBit 依赖项的项目,在运行 moon update 之前,执行 moon check / moon test / moon build 都会失败,并提示 Failed to resolve registry dependency。在每个涉及 mooncakes 的工作流中,都应在安装 CLI 后立即运行 moon update。
发布构建依赖 MoonBit 的 npm 包
有些项目(例如 Vite 插件、语言工具)以 npm 包的形式发布,但会在 pnpm build 中运行 moon——例如,pnpm build:parser 会调用 moon -C tools/parser build --release --target js。在这种情况下,npm 发布工作流需要在 pnpm build 之前同时安装 MoonBit CLI 并运行 moon update,否则构建会在 CI 上失败。
请参阅 assets/publish-to-npm.yaml,了解一个由发布触发的精简工作流示例。它使用 OIDC Trusted Publishing(无需 NPM_TOKEN),并正确配置了 MoonBit。搭配发布自动化工具使用(请参阅 npm-release 技能,了解 release-please + OIDC 配置),以避免手动调用 npm publish。
发布到 mooncakes.io
包内容由 .moonignore 过滤(若不存在则使用 .gitignore);
默认排除以点开头的文件和 _build/。mooncakes.io 不接受仅大小写不同的
包名。发布前请验证制品:
moon publish --dry-run -v
unzip -l _build/publish/<author>-<module>-<version>.zip
资源
assets/moon.mod— DSL 格式的模块配置示例(替代 moon.mod.json)assets/moon.pkg— DSL 格式的包配置示例(替代 moon.pkg.json)assets/ci.yaml— GitHub Actions CI(curl 安装程序)assets/ci-nix.yaml— 使用 Nix 的 GitHub Actions CI(moonbit-overlay)assets/publish-to-npm.yaml— 由发布触发的 npm 发布示例,包含 MoonBit 构建和 OIDC Trusted Publishing
Nix 配置(moonbit-overlay)
moonbit-community/moonbit-overlay 提供了用于可复现 MoonBit 构建的 Nix flake overlay。
MoonBit 项目的精简 flake.nix:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
moonbit-overlay.url = "github:moonbit-community/moonbit-overlay";
moon-registry = {
url = "git+https://mooncakes.io/git/index";
flake = false;
};
};
outputs = { nixpkgs, moonbit-overlay, moon-registry, ... }:
let
system = "x86_64-linux"; # or aarch64-darwin, etc.
pkgs = import nixpkgs {
inherit system;
overlays = [ moonbit-overlay.overlays.default ];
};
moonHome = pkgs.moonPlatform.bundleWithRegistry {
cachedRegistry = pkgs.moonPlatform.buildCachedRegistry {
moonModJson = ./moon.mod.json;
registryIndexSrc = moon-registry;
};
};
in {
devShells.${system}.default = pkgs.mkShellNoCC {
packages = [ moonHome pkgs.git ];
env.MOON_HOME = "${moonHome}";
};
};
}
该 overlay 的主要 API:
pkgs.moonPlatform.buildMoonPackage- 将 MoonBit 包构建为 Nix 派生项pkgs.moonPlatform.bundleWithRegistry- 创建包含缓存注册表的 MOON_HOMEpkgs.moonPlatform.buildCachedRegistry- 预先获取 mooncakes 注册表依赖项
该 overlay 的
moonModJson参数仍需要 JSON 清单。如果你的模块已迁移到moon.mod,请为 Nix 保留生成的moon.mod.json,或在移除它之前检查该 overlay 是否支持 DSL。