moon_zod/moonbit_syntax_pitfalls.md

615 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 # 添加依赖
```