14 KiB
MoonBit 语法避坑指南
基于
moon_zod项目开发过程中踩过的真实错误汇总。
目录
1. 类型与变量
PascalCase 是禁区
变量必须用小写 snake_case,PascalCase 是类型/枚举/构造器的保留命名规则。
// ❌ 编译错误:unbound variable
let UserSchema = object({ ... })
// ✅ 正确
let user_schema = object({ ... })
全局变量需要类型注解
顶层的 let 绑定(非函数内部)经常需要显式写出类型:
// ❌ 类型推断失败
let schema = @moon_zod.object({ ... })
// ✅ 显式注解
let schema : @moon_zod.Schema = @moon_zod.object({ ... })
pub vs pub(all)
| 关键字 | 适用对象 | 说明 |
|---|---|---|
fn |
函数 | 仅包内可见 |
pub fn |
函数 | 外部包可见 |
pub(all) enum/struct |
类型 | 外部包可见 |
// ❌ pub(all) 不能用于函数
pub(all) fn bench(n : Int) -> Bool { ... }
// ✅ 正确
pub fn bench(n : Int) -> Bool { ... }
没有三元运算符
MoonBit 没有 cond ? a : b,用 if-else 表达式替代:
let label = if n > 0 { "positive" } else { "non-positive" }
lazy 是保留关键字
MoonBit 已预留 lazy 关键字,不能用作函数名或变量名:
// ❌ 警告:lazy is reserved for possible future use
pub fn lazy(f : () -> Schema) -> Schema { ... }
// ✅ 改用其他名称
pub fn recursive(f : () -> Schema) -> Schema { ... }
let rec 只支持函数
let rec 只能用于递归函数定义,不能用于递归值:
// ❌ 编译错误:The value identifier tree is unbound
let rec tree = object({ "children": array(recursive(fn() { tree })).optional() })
// ✅ 用函数模式包装
fn tree_schema() -> Schema {
recursive(fn() {
object({
"value": number(),
"children": array(recursive(tree_schema)).optional(),
})
})
}
[] 创建空数组需要类型注解
let errors : Array[ValidationError] = [] // 需要类型
let tags = ["rust", "wasm", "ai"] // 有初始值可推断
Result 是 Ok/Err,不是 Ok/Error
// ❌ Error 不是 MoonBit 的 Result 变体
let r : Result[Int, String] = Error("bad")
// ✅ 正确
let r : Result[Int, String] = Err("bad")
2. 函数与流程控制
fn main 中不能使用 Result-returning 函数
fn main 是 is-main 入口,不支持 Result 返回类型的函数(如 @json.parse())。必须通过 catch 语法处理:
// ❌ 错误:Function with error is not allowed in `fn main`
let json = @json.parse(raw) match {
Ok(v) => v
Err(_) => return
}
// ✅ 正确:用 catch 处理
let json = @json.parse(raw) catch {
_ => {
println("Error: invalid JSON")
return
}
}
Match 臂内联时需 ; 分隔分支
当 match 写在一行时,分支之间需要 ; 分隔:
// ❌ 解析错误:unexpected token `_`
match json { Number(v, ..) => v > 0.0 _ => false }
// ✅ 正确:换行或者用 ; 分隔
match json {
Number(v, ..) => v > 0.0
_ => false
}
match json { Number(v, ..) => v > 0.0; _ => false }
Match 分支返回值必须一致
所有 match arm 必须返回相同类型:
// ❌ 类型不一致
match mode {
"moonzod" => bench(n) // 返回 Bool
"startup" => println("done") // 返回 Unit
}
// ✅ 用 let _ = 消化不需要的返回值
match mode {
"moonzod" => { let _ = bench(n) }
"startup" => println("done")
}
方法定义已弃用旧语法
新版已弃用 fn method(self : Type, ...),必须用 fn Type::method(self, ...):
// ❌ deprecated_syntax 警告
pub fn append_rule(self : Schema, check : ...) -> Schema { ... }
// ✅ 正确
pub fn Schema::append_rule(self, check : ...) -> Schema { ... }
3. 数据结构与集合
可变变量用 let mut
let mut i = 0
i = i + 1 // 重新绑定需要 mut
let mut 对 Array/Map 的误区
Array/Map 的内容修改不需要 mut,只有重新绑定变量才需要:
let errors : Array[ValidationError] = []
// ✅ push 不需要 mut(修改内容)
errors.push(ValidationError::{ ... })
// ❌ 重新赋值需要 mut
errors = [] // ← 需要 let mut errors
Map 取值返回 Option
match map.get("name") {
Some(value) => // 存在
None => // 不存在
}
Array pop() 返回 Option[T]
let top = stack.pop() // Option[String]
let _ = stack.pop() // 忽略 None
Map 方法返回 Iter,不是 Array
Map.keys()、Map.values() 返回 Iter 类型,不能直接链式调用 Array 方法:
// ❌ 类型错误:Expr Type Mismatch, has type Iter[T], wanted Array[T]
let keys_json : Array[Json] = options.keys().map(fn(k) { Json::string(k) })
// ✅ 手动收集
let keys_json : Array[Json] = []
for k in options.keys() {
keys_json.push(Json::string(k))
}
有载荷的 enum 变体用 ::{} 构造
// ❌ 编译错误
ValidationError("x", "bad", json)
// ✅ 正确
ValidationError::{ path: "x", message: "bad", got: json }
结构体更新用 .. 展开
{ ..schema, rules: schema.rules + [new_rule] }
For 循环两种写法
for i = 0; i < n; i = i + 1 { ... } // C 风格
for element in array { ... } // 迭代器风格
for i, element in array { ... } // 带索引
4. Json 处理
构造器 vs 模式匹配
最容易踩的坑:构造用小写 Json::object(),模式匹配用大写 Object()。
// ✅ 构造
let data = Json::object({ "name": Json::string("Alice") })
// ✅ 模式匹配
match data {
Object(map) => map.get("name")
_ => None
}
// ❌ 错误:Cannot create values of the read-only type
let bad = Json::Object({ ... })
同理 Json::array()/Array()、Json::string()/String()。
Json 模式匹配注意 ..
match json {
Number(v, ..) => // .. 必须,忽略额外字段
Object(map) => ...
Array(elements) => ...
}
字符串插值用 \{ }
// ❌ ${n} 被当作普通文本
println("n = ${n}") // 输出: n = ${n}
// ✅ 正确
println("n = \{n}")
5. 模块与包
子包必须加 @ 模块前缀
子包(如 cmd/wasm/)引用上层模块时必须加前缀:
// ❌ 找不到 object
let schema = object({ ... })
// ✅ 正确
let schema = @moon_zod.object({ ... })
is-main 包不能被 import
moon.pkg 中标记 is-main: true 的包是独立可执行入口,不能被其他包 import。
MoonBit 核心包无需显式导入
moonbitlang/core/json 等核心包新版已自动处理,不需要在 moon.pkg 中声明。
// moon.pkg — 核心包可省略
import {
// "moonbitlang/core/json", ← 新版可省略
"Betterlol/moon_zod", // 外部包仍需声明
}
6. 测试
test 文件不能写 import
测试文件(test_*.mbt)不能使用 import { ... } 语法。依赖需在 moon.pkg 中声明:
// ❌ test_xxx.mbt 中不能写 import
import { "Betterlol/moon_zod" } // 编译错误
// ✅ 在 moon.pkg 中声明依赖
@debug.assert_eq 只有两个参数
// ❌ 错误:given 3 positional arguments
@debug.assert_eq(a, b, "custom message")
// ✅ 正确
@debug.assert_eq(a, b)
moon test 只能发现根目录的 test_*.mbt
子目录(如 cmd/validate/)中的测试文件不会被自动发现。
没有 @os.execute() / @process
无法在单元测试中执行外部命令。CLI 功能需通过外部 shell 脚本验证。
7. Wasm 特定
只导出 _start
MoonBit --target wasm 编译后,只有 _start 和 memory 是导出项,pub fn 不会成为独立 Wasm 导出函数。
// ❌ 不存在
wasm.exports.some_function()
// ✅ 只有 _start
wasm.exports._start()
解决方案:CLI 参数分发模式。
fn main {
let args = @env.args()
let mode = args.get(1).unwrap_or("default")
match mode {
"bench" => { let _ = run_benchmark() }
_ => println("usage")
}
}
8. Trait 与泛型
Trait 定义语法
Trait 方法签名中的参数必须有名字,不能只写类型:
// ❌ 编译错误:unexpected token id (uppercase start)
pub(open) trait Renderer {
fn render_circle(Self, Double) -> String
}
// ✅ 正确:参数需要名字
pub(open) trait Renderer {
fn render_circle(Self, radius: Double) -> String
fn render_rect(Self, w: Double, h: Double) -> String
}
Trait 方法不能有方法体
Trait 定义中只能写签名,不能有 { } 方法体:
// ❌ 编译错误:unexpected token `{`
pub(open) trait Renderer {
fn render(Self, Renderable) -> String { ... }
}
// ✅ 正确:只在 trait 里写签名
pub(open) trait Renderer {
fn render_circle(Self, radius: Double) -> String
fn render_rect(Self, w: Double, h: Double) -> String
}
默认方法实现用 impl Trait with
如果所有实现类型共享某个默认方法,写在独立的 impl Trait with fn 块中(注意:无 for Type):
impl Renderer with fn render_inches(self, cm: Double) -> String {
self.render_circle(cm / 2.54) // 可调用 trait 中其他方法
}
Trait 实现必须用 with fn Type::method
pub struct SimpleRenderer {}
// ✅ 正确语法
pub impl Renderer for SimpleRenderer with fn render_circle(
self: SimpleRenderer,
radius: Double
) -> String {
"circle:" + radius.to_string()
}
泛型函数 —— fn[...] 在函数名前
新版语法:fn 关键字后立刻跟 [泛型参数],再跟函数名:
// ❌ 弃用语法 (deprecated_syntax 警告)
fn render[R : Renderer](renderer: R, st: Shape) -> String { ... }
// ✅ 正确
fn[R : Renderer] render(renderer: R, st: Shape) -> String { ... }
不支持带类型参数的 Trait
MoonBit 不支持 trait Foo[T],也不支持关联类型:
// ❌ 编译错误:unexpected token `[`
pub(open) trait SchemaRenderer[T] {
fn render_string(Self, s: Schema) -> T
}
// ✅ 正确做法:每个返回类型定义一个独立 trait
pub(open) trait StringRenderer {
fn render_string(Self, s: Schema) -> String
}
pub(open) trait JsonRenderer {
fn render_string(Self, s: Schema) -> Json
}
不能把 Trait 当参数类型
Trait 名不能直接作参数类型,必须用泛型约束:
// ❌ 编译错误:Renderer is a trait, not a type
fn render(renderer: Renderer, shape: Shape) -> String { ... }
// ✅ 正确:用泛型约束
fn[R : Renderer] render(renderer: R, shape: Shape) -> String { ... }
结构体泛型参数不支持 Trait 约束
结构体定义中,类型参数不能用 : Trait 约束。约束只能加在方法上:
// ❌ 编译错误:unexpected token `:`
pub struct Wrapper[R : Renderer] {
inner: R
}
// ✅ 正确:结构体只写类型名
pub struct Wrapper[R] {
inner: R
}
// 约束加在方法上
fn[R : Renderer] Wrapper::process(self: Wrapper[R], shape: Shape) -> String { ... }
Struct 不会自动生成构造函数
MoonBit 的 struct 必须显式声明构造函数,否则无法用 Type(args) 语法创建:
pub struct Point {
x: Int
y: String
}
// 必须写构造函数
pub fn Point::Point(x~ : Int, y~ : String) -> Point {
{ x, y } // ~ 标记字段名即参数名
}
// 泛型结构体的构造函数
pub struct Wrapper[T] {
inner: T
prefix: String
}
pub[T] fn Wrapper::Wrapper(inner~ : T, prefix~ : String) -> Wrapper[T] {
{ inner, prefix }
}
构造函数的参数标记 ~ 表示字段名即参数名(label punning),调用时:
let p = Point(x=10, y="hello")
let w = Wrapper(inner=some_val, prefix="tag")
空结构体的构造
空结构体需要用 Type::{} 语法在构造函数体中使用:
pub struct Empty {}
pub fn Empty::Empty() -> Empty {
Empty::{} // ← 不是 {},{} 会被解析为 Map 字面量
}
Match 臂中 { } 块可能引起解析歧义
在返回 Json 等需要推断类型的 match 臂中使用 { let ...; expr } 块,可能触发编译器关于表达式的歧义警告。如有歧义,可抽提为独立函数或简化 match 臂:
// ⚠️ 可能触发 "value cannot be implicitly ignored" 级联错误
DiscriminatedUnionType(_, options) => {
let schemas : Array[@core.Schema] = []
for _key, option in options { schemas.push(option) }
renderer.render_union(schemas, schema)
}
// ✅ 简化为单一表达式
DiscriminatedUnionType(_, _) => Json::null()
字符串中 \{ 永远是插值
MoonBit 字符串插值用 \{expr},无法在插值字符串中包含字面量 {:
// ❌ 编译错误:\{ radius: 被解析为插值开始
"Circle \{ radius: \{r} }"
// ✅ 正确:用 + 拼接
"Circle " + radius.to_string()
// ✅ 或用多行字符串 $|...|#
$|{ "radius": \{r} }|
附录:运行命令
moon test # 跑测试
moon build # 原生编译
moon build --target wasm --release # Wasm 编译
moon run cmd/validate -- --help # 运行 CLI
moon info && moon fmt # 更新接口 + 格式化
moon add moonbitlang/x # 添加依赖