615 lines
14 KiB
Markdown
615 lines
14 KiB
Markdown
# MoonBit 语法避坑指南
|
||
|
||
> 基于 `moon_zod` 项目开发过程中踩过的真实错误汇总。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [类型与变量](#1-类型与变量)
|
||
- [函数与流程控制](#2-函数与流程控制)
|
||
- [数据结构与集合](#3-数据结构与集合)
|
||
- [Json 处理](#4-json-处理)
|
||
- [模块与包](#5-模块与包)
|
||
- [测试](#6-测试)
|
||
- [Wasm 特定](#7-wasm-特定)
|
||
- [Trait 与泛型](#8-trait-与泛型)
|
||
- [附录:运行命令](#附录运行命令)
|
||
|
||
---
|
||
|
||
## 1. 类型与变量
|
||
|
||
### PascalCase 是禁区
|
||
|
||
变量必须用小写 `snake_case`,PascalCase 是类型/枚举/构造器的保留命名规则。
|
||
|
||
```mbt
|
||
// ❌ 编译错误:unbound variable
|
||
let UserSchema = object({ ... })
|
||
|
||
// ✅ 正确
|
||
let user_schema = object({ ... })
|
||
```
|
||
|
||
### 全局变量需要类型注解
|
||
|
||
顶层的 `let` 绑定(非函数内部)经常需要显式写出类型:
|
||
|
||
```mbt
|
||
// ❌ 类型推断失败
|
||
let schema = @moon_zod.object({ ... })
|
||
|
||
// ✅ 显式注解
|
||
let schema : @moon_zod.Schema = @moon_zod.object({ ... })
|
||
```
|
||
|
||
### `pub` vs `pub(all)`
|
||
|
||
| 关键字 | 适用对象 | 说明 |
|
||
|---|---|---|
|
||
| `fn` | 函数 | 仅包内可见 |
|
||
| `pub fn` | 函数 | 外部包可见 |
|
||
| `pub(all) enum/struct` | 类型 | 外部包可见 |
|
||
|
||
```mbt
|
||
// ❌ pub(all) 不能用于函数
|
||
pub(all) fn bench(n : Int) -> Bool { ... }
|
||
|
||
// ✅ 正确
|
||
pub fn bench(n : Int) -> Bool { ... }
|
||
```
|
||
|
||
### 没有三元运算符
|
||
|
||
MoonBit 没有 `cond ? a : b`,用 `if-else` 表达式替代:
|
||
|
||
```mbt
|
||
let label = if n > 0 { "positive" } else { "non-positive" }
|
||
```
|
||
|
||
### `lazy` 是保留关键字
|
||
|
||
MoonBit 已预留 `lazy` 关键字,不能用作函数名或变量名:
|
||
|
||
```mbt
|
||
// ❌ 警告:lazy is reserved for possible future use
|
||
pub fn lazy(f : () -> Schema) -> Schema { ... }
|
||
|
||
// ✅ 改用其他名称
|
||
pub fn recursive(f : () -> Schema) -> Schema { ... }
|
||
```
|
||
|
||
### `let rec` 只支持函数
|
||
|
||
`let rec` 只能用于递归**函数**定义,不能用于递归**值**:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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(),
|
||
})
|
||
})
|
||
}
|
||
```
|
||
|
||
### `[]` 创建空数组需要类型注解
|
||
|
||
```mbt
|
||
let errors : Array[ValidationError] = [] // 需要类型
|
||
let tags = ["rust", "wasm", "ai"] // 有初始值可推断
|
||
```
|
||
|
||
### Result 是 `Ok`/`Err`,不是 `Ok`/`Error`
|
||
|
||
```mbt
|
||
// ❌ 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` 语法处理:
|
||
|
||
```mbt
|
||
// ❌ 错误: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 写在一行时,分支之间需要 `;` 分隔:
|
||
|
||
```mbt
|
||
// ❌ 解析错误: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 必须返回相同类型:
|
||
|
||
```mbt
|
||
// ❌ 类型不一致
|
||
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, ...)`:
|
||
|
||
```mbt
|
||
// ❌ deprecated_syntax 警告
|
||
pub fn append_rule(self : Schema, check : ...) -> Schema { ... }
|
||
|
||
// ✅ 正确
|
||
pub fn Schema::append_rule(self, check : ...) -> Schema { ... }
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 数据结构与集合
|
||
|
||
### 可变变量用 `let mut`
|
||
|
||
```mbt
|
||
let mut i = 0
|
||
i = i + 1 // 重新绑定需要 mut
|
||
```
|
||
|
||
### `let mut` 对 Array/Map 的误区
|
||
|
||
Array/Map 的**内容修改不需要 `mut`**,只有**重新绑定变量**才需要:
|
||
|
||
```mbt
|
||
let errors : Array[ValidationError] = []
|
||
|
||
// ✅ push 不需要 mut(修改内容)
|
||
errors.push(ValidationError::{ ... })
|
||
|
||
// ❌ 重新赋值需要 mut
|
||
errors = [] // ← 需要 let mut errors
|
||
```
|
||
|
||
### Map 取值返回 `Option`
|
||
|
||
```mbt
|
||
match map.get("name") {
|
||
Some(value) => // 存在
|
||
None => // 不存在
|
||
}
|
||
```
|
||
|
||
### Array `pop()` 返回 `Option[T]`
|
||
|
||
```mbt
|
||
let top = stack.pop() // Option[String]
|
||
let _ = stack.pop() // 忽略 None
|
||
```
|
||
|
||
### Map 方法返回 Iter,不是 Array
|
||
|
||
`Map.keys()`、`Map.values()` 返回 `Iter` 类型,不能直接链式调用 `Array` 方法:
|
||
|
||
```mbt
|
||
// ❌ 类型错误: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 变体用 `::{}` 构造
|
||
|
||
```mbt
|
||
// ❌ 编译错误
|
||
ValidationError("x", "bad", json)
|
||
|
||
// ✅ 正确
|
||
ValidationError::{ path: "x", message: "bad", got: json }
|
||
```
|
||
|
||
### 结构体更新用 `..` 展开
|
||
|
||
```mbt
|
||
{ ..schema, rules: schema.rules + [new_rule] }
|
||
```
|
||
|
||
### For 循环两种写法
|
||
|
||
```mbt
|
||
for i = 0; i < n; i = i + 1 { ... } // C 风格
|
||
for element in array { ... } // 迭代器风格
|
||
for i, element in array { ... } // 带索引
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Json 处理
|
||
|
||
### 构造器 vs 模式匹配
|
||
|
||
**最容易踩的坑**:构造用小写 `Json::object()`,模式匹配用大写 `Object()`。
|
||
|
||
```mbt
|
||
// ✅ 构造
|
||
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 模式匹配注意 `..`
|
||
|
||
```mbt
|
||
match json {
|
||
Number(v, ..) => // .. 必须,忽略额外字段
|
||
Object(map) => ...
|
||
Array(elements) => ...
|
||
}
|
||
```
|
||
|
||
### 字符串插值用 `\{ }`
|
||
|
||
```mbt
|
||
// ❌ ${n} 被当作普通文本
|
||
println("n = ${n}") // 输出: n = ${n}
|
||
|
||
// ✅ 正确
|
||
println("n = \{n}")
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 模块与包
|
||
|
||
### 子包必须加 `@` 模块前缀
|
||
|
||
子包(如 `cmd/wasm/`)引用上层模块时必须加前缀:
|
||
|
||
```mbt
|
||
// ❌ 找不到 object
|
||
let schema = object({ ... })
|
||
|
||
// ✅ 正确
|
||
let schema = @moon_zod.object({ ... })
|
||
```
|
||
|
||
### `is-main` 包不能被 import
|
||
|
||
`moon.pkg` 中标记 `is-main: true` 的包是独立可执行入口,不能被其他包 import。
|
||
|
||
### MoonBit 核心包无需显式导入
|
||
|
||
`moonbitlang/core/json` 等核心包**新版已自动处理**,不需要在 `moon.pkg` 中声明。
|
||
|
||
```toml
|
||
// moon.pkg — 核心包可省略
|
||
import {
|
||
// "moonbitlang/core/json", ← 新版可省略
|
||
"Betterlol/moon_zod", // 外部包仍需声明
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 测试
|
||
|
||
### test 文件不能写 import
|
||
|
||
测试文件(`test_*.mbt`)**不能使用 `import { ... }` 语法**。依赖需在 `moon.pkg` 中声明:
|
||
|
||
```mbt
|
||
// ❌ test_xxx.mbt 中不能写 import
|
||
import { "Betterlol/moon_zod" } // 编译错误
|
||
|
||
// ✅ 在 moon.pkg 中声明依赖
|
||
```
|
||
|
||
### `@debug.assert_eq` 只有两个参数
|
||
|
||
```mbt
|
||
// ❌ 错误: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 导出函数。
|
||
|
||
```js
|
||
// ❌ 不存在
|
||
wasm.exports.some_function()
|
||
|
||
// ✅ 只有 _start
|
||
wasm.exports._start()
|
||
```
|
||
|
||
**解决方案**:CLI 参数分发模式。
|
||
|
||
```mbt
|
||
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 方法签名中的参数**必须有名字**,不能只写类型:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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 定义中只能写签名,**不能有 `{ }` 方法体**:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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`):
|
||
|
||
```mbt
|
||
impl Renderer with fn render_inches(self, cm: Double) -> String {
|
||
self.render_circle(cm / 2.54) // 可调用 trait 中其他方法
|
||
}
|
||
```
|
||
|
||
### Trait 实现必须用 `with fn Type::method`
|
||
|
||
```mbt
|
||
pub struct SimpleRenderer {}
|
||
|
||
// ✅ 正确语法
|
||
pub impl Renderer for SimpleRenderer with fn render_circle(
|
||
self: SimpleRenderer,
|
||
radius: Double
|
||
) -> String {
|
||
"circle:" + radius.to_string()
|
||
}
|
||
```
|
||
|
||
### 泛型函数 —— `fn[...]` 在函数名前
|
||
|
||
**新版语法**:`fn` 关键字后立刻跟 `[泛型参数]`,再跟函数名:
|
||
|
||
```mbt
|
||
// ❌ 弃用语法 (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]`,也不支持关联类型:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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 名不能直接作参数类型,必须用泛型约束:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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` 约束。约束只能加在方法上:
|
||
|
||
```mbt
|
||
// ❌ 编译错误: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)` 语法创建:
|
||
|
||
```mbt
|
||
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),调用时:
|
||
|
||
```mbt
|
||
let p = Point(x=10, y="hello")
|
||
let w = Wrapper(inner=some_val, prefix="tag")
|
||
```
|
||
|
||
### 空结构体的构造
|
||
|
||
空结构体需要用 `Type::{}` 语法在构造函数体中使用:
|
||
|
||
```mbt
|
||
pub struct Empty {}
|
||
pub fn Empty::Empty() -> Empty {
|
||
Empty::{} // ← 不是 {},{} 会被解析为 Map 字面量
|
||
}
|
||
```
|
||
|
||
### Match 臂中 `{ }` 块可能引起解析歧义
|
||
|
||
在返回 `Json` 等需要推断类型的 match 臂中使用 `{ let ...; expr }` 块,可能触发编译器关于表达式的歧义警告。如有歧义,可抽提为独立函数或简化 match 臂:
|
||
|
||
```mbt
|
||
// ⚠️ 可能触发 "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}`,**无法在插值字符串中包含字面量 `{`**:
|
||
|
||
```mbt
|
||
// ❌ 编译错误:\{ radius: 被解析为插值开始
|
||
"Circle \{ radius: \{r} }"
|
||
|
||
// ✅ 正确:用 + 拼接
|
||
"Circle " + radius.to_string()
|
||
|
||
// ✅ 或用多行字符串 $|...|#
|
||
$|{ "radius": \{r} }|
|
||
```
|
||
|
||
---
|
||
|
||
## 附录:运行命令
|
||
|
||
```bash
|
||
moon test # 跑测试
|
||
moon build # 原生编译
|
||
moon build --target wasm --release # Wasm 编译
|
||
moon run cmd/validate -- --help # 运行 CLI
|
||
moon info && moon fmt # 更新接口 + 格式化
|
||
moon add moonbitlang/x # 添加依赖
|
||
``` |