moonbit-agent-guide

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Agent Workflow

Agent工作流

For fast, reliable task execution, follow this order:
  1. Clarify goal and constraints
    • Confirm expected behavior, non-goals, and compatibility constraints (target backend, public API stability, performance limits).
  2. Locate module/package boundaries
    • Find
      moon.mod.json
      (module root) and relevant
      moon.pkg
      files (package boundaries and imports).
  3. Discover APIs before coding
    • Prefer
      moon ide doc
      queries to discover existing functions/types/methods before adding new code.
    • Use
      moon ide outline
      ,
      moon ide peek-def
      , and
      moon ide find-references
      for semantic navigation.
  4. Reliable refactoring
    • Use
      moon ide rename
      for semantic refactoring. If multiple symbols share a name, add
      --loc filename:line:col
      .
    • If you want maintain backwards compatibility, use
      #alias(old_api, deprecated)
      .
  5. Edit minimally and package-locally
    • Keep changes inside the correct package, use
      ///|
      top-level delimiters, and split code into cohesive files.
  6. Validate in a tight loop
    • Run
      moon check
      after edits, adding
      --warn-list +unnecessary_annotation
      to enable warning 73 for redundant annotations and over-qualified constructors (
      --warn-list +73
      is equivalent).
    • Run targeted tests with
      moon test [dirname|filename] --filter 'glob'
      and use
      moon test --update
      for snapshot changes.
  7. Finalize before handoff
    • Run
      moon fmt
      .
    • Run
      moon info
      to verify whether public APIs changed (
      pkg.generated.mbti
      diff).
    • Report changed files, validation commands, and any remaining risks.
为了快速、可靠地执行任务,请遵循以下步骤:
  1. 明确目标与约束
    • 确认预期行为、非目标需求以及兼容性约束(目标后端、公共API稳定性、性能限制)。
  2. 定位模块/包边界
    • 找到
      moon.mod.json
      (模块根目录标识)和相关
      moon.pkg
      文件(包边界与导入配置)。
  3. 编码前先探索API
    • 在添加新代码前,优先使用
      moon ide doc
      查询来发现现有函数/类型/方法。
    • 使用
      moon ide outline
      moon ide peek-def
      moon ide find-references
      进行语义导航。
  4. 可靠重构
    • 使用
      moon ide rename
      进行语义重构。若多个符号重名,添加
      --loc filename:line:col
      参数指定位置。
    • 如需保持向后兼容性,使用
      #alias(old_api, deprecated)
  5. 最小化编辑并保持包内本地化
    • 将修改限制在正确的包内,使用
      ///|
      顶级分隔符,将代码拆分为内聚性强的文件。
  6. 循环验证
    • 编辑后运行
      moon check
      ,添加
      --warn-list +unnecessary_annotation
      启用警告73(冗余注解和过度限定构造函数,
      --warn-list +73
      等效)。
    • 使用
      moon test [dirname|filename] --filter 'glob'
      运行针对性测试,使用
      moon test --update
      处理快照变更。
  7. 交付前最终确认
    • 运行
      moon fmt
      格式化代码。
    • 运行
      moon info
      验证公共API是否变更(对比
      pkg.generated.mbti
      差异)。
    • 报告变更文件、验证命令以及剩余风险。

Fast Task Playbooks

快速任务执行手册

Use the smallest playbook that matches the request.
选择与需求匹配的最小执行流程。

Bug Fix (No API Change Intended)

Bug修复(无API变更需求)

  1. Reproduce or identify the failing behavior.
  2. Locate symbols with
    moon ide outline
    ,
    moon ide peek-def
    ,
    moon ide find-references
    .
  3. Implement minimal fix in the current package.
  4. Validate with:
    • moon check
    • moon test [dirname|filename] --filter 'glob'
      (or closest targeted test scope)
    • moon fmt
    • moon info
      (confirm
      pkg.generated.mbti
      unchanged)
  1. 复现或定位故障行为。
  2. 使用
    moon ide outline
    moon ide peek-def
    moon ide find-references
    定位符号。
  3. 在当前包内实现最小修复。
  4. 验证步骤:
    • moon check
    • moon test [dirname|filename] --filter 'glob'
      (或最接近的针对性测试范围)
    • moon fmt
    • moon info
      (确认
      pkg.generated.mbti
      无变更)

Refactor (Behavior Preserving)

重构(保留行为)

  1. Confirm behavior/API invariants first.
  2. Prefer semantic rename/navigation tools:
    • moon ide rename
    • moon ide find-references
    • moon ide peek-def
    • If multiple symbols share a name, use
      moon ide rename <symbol> <new_name> --loc filename:line:col
      .
  3. Keep edits package-local and file-organization-focused.
  4. Validate with:
    • moon check
    • moon test [dirname|filename]
    • moon fmt
    • moon info
      (API should remain unchanged unless requested)
  1. 先确认行为/API不变量。
  2. 优先使用语义重命名/导航工具:
    • moon ide rename
    • moon ide find-references
    • moon ide peek-def
    • 若多个符号重名,使用
      moon ide rename <symbol> <new_name> --loc filename:line:col
  3. 保持编辑在包内,聚焦文件组织优化。
  4. 验证步骤:
    • moon check
    • moon test [dirname|filename]
    • moon fmt
    • moon info
      (除非有需求,否则API应保持不变)

New Feature or Public API

新功能或公共API开发

  1. Discover existing idioms with
    moon ide doc
    before introducing new names.
  2. Add implementation in cohesive files with
    ///|
    delimiters.
  3. Add/extend black-box tests and docstring examples for public APIs.
  4. Validate with:
    • moon check
    • moon test [dirname|filename]
      (use
      --update
      for snapshots when needed)
    • moon fmt
    • moon info
      (review and keep intended
      pkg.generated.mbti
      changes)
  1. 在引入新名称前,使用
    moon ide doc
    探索现有编程范式。
  2. 使用
    ///|
    分隔符将实现代码放在内聚性文件中。
  3. 为公共API添加/扩展黑盒测试和文档字符串示例。
  4. 验证步骤:
    • moon check
    • moon test [dirname|filename]
      (必要时使用
      --update
      更新快照)
    • moon fmt
    • moon info
      (查看并保留预期的
      pkg.generated.mbti
      变更)

MoonBit Project Layouts

MoonBit项目布局

MoonBit uses the
.mbt
extension for source code files and interface files with the
.mbti
extension. At the top-level of a MoonBit project there is a
moon.mod.json
file specifying the metadata of the project. The project may contain multiple packages, each with its own
moon.pkg
. Subdirectories may also contain
moon.mod.json
files indicating that a different set of dependencies can be used for that subdir.
MoonBit使用
.mbt
作为源代码文件扩展名,
.mbti
作为接口文件扩展名。MoonBit项目的顶级目录下有一个
moon.mod.json
文件,用于指定项目元数据。项目可包含多个包,每个包都有自己的
moon.pkg
文件。子目录也可能包含
moon.mod.json
文件,表示该子目录可使用不同的依赖集。

Example layout

示例布局

my_module
├── moon.mod.json             # Module metadata, source field (optional) specifies the source directory of the module
├── moon.pkg             # Package metadata (each directory is a package like Golang)
├── README.mbt.md             # Markdown with tested code blocks (`test "..." { ... }`)
├── README.md -> README.mbt.md
├── cmd                       # Command line directory
│   └── main
│       ├── main.mbt
│       └── moon.pkg     # executable package with `options("is-main": true)`
├── liba/                     # Library packages
│   └── moon.pkg         # Referenced by other packages as `@username/my_module/liba`
│   └── libb/                 # Library packages
│       └── moon.pkg     # Referenced by other packages as `@username/my_module/liba/libb`
├── user_pkg.mbt              # Root packages, referenced by other packages as `@username/my_module`
├── user_pkg_wbtest.mbt       # White-box tests (only needed for testing internal private members, similar to Golang's package mypackage)
└── user_pkg_test.mbt         # Black-box tests
└── ...                       # More package files, symbols visible to current package (like Golang)
  • Module: characterized by a
    moon.mod.json
    file in the project root directory. A MoonBit module is like a Go module; it is a collection of packages in subdirectories, usually corresponding to a repository or project. Module boundaries matter for dependency management and import paths.
  • Package: characterized by a
    moon.pkg
    file in each directory. All subcommands of
    moon
    will still be executed in the directory of the module (where
    moon.mod.json
    is located), not the current package. A MoonBit package is the actual compilation unit (like a Go package). All source files in the same package are concatenated into one unit and thereby share all definitions throughout that package. The
    name
    in the
    moon.mod.json
    file combined with the relative path to the package source directory defines the package name, not the file name. Imports refer to module + package paths, NEVER to file names.
  • Files: A
    .mbt
    file is just a chunk of source code inside a package. File names do NOT create modules, packages, or namespaces. You may freely split/merge/move declarations between files in the same package. Any declaration in a package can reference any other declaration in that package, regardless of file.
my_module
├── moon.mod.json             # 模块元数据,source字段(可选)指定模块的源目录
├── moon.pkg             # 包元数据(每个目录对应一个包,类似Golang)
├── README.mbt.md             # 包含可测试代码块的Markdown文件(`test "..." { ... }`)
├── README.md -> README.mbt.md
├── cmd                       # 命令行工具目录
│   └── main
│       ├── main.mbt
│       └── moon.pkg     # 可执行包,配置`options("is-main": true)`
├── liba/                     # 库包
│   └── moon.pkg         # 其他包可通过`@username/my_module/liba`引用
│   └── libb/                 # 库包
│       └── moon.pkg     # 其他包可通过`@username/my_module/liba/libb`引用
├── user_pkg.mbt              # 根包,其他包可通过`@username/my_module`引用
├── user_pkg_wbtest.mbt       # 白盒测试文件(仅用于测试内部私有成员,类似Golang的package mypackage)
└── user_pkg_test.mbt         # 黑盒测试文件
└── ...                       # 更多包文件,符号对当前包可见(类似Golang)
  • 模块:由项目根目录下的
    moon.mod.json
    文件标识。 MoonBit模块类似Go模块,是子目录中包的集合,通常对应一个仓库或项目。 模块边界对依赖管理和导入路径至关重要。
  • :由每个目录下的
    moon.pkg
    文件标识。
    moon
    的所有子命令仍在模块目录(
    moon.mod.json
    所在目录)中执行,而非当前包目录。 MoonBit是实际的编译单元(类似Go包)。 同一包内的所有源文件会被合并为一个单元,因此包内所有定义都可互相引用。 包名称由
    moon.mod.json
    中的
    name
    字段加上包源目录的相对路径定义,而非文件名。 导入引用的是模块+包路径,绝不是文件名。
  • 文件:
    .mbt
    文件只是包内的一段源代码。 文件名不会创建模块、包或命名空间。 你可以自由地在同一包内的文件之间拆分/合并/移动声明。 包内的任何声明都可以引用同一包内的其他声明,无论所在文件是什么。

Coding/layout rules you MUST follow:

必须遵循的编码/布局规则:

  1. Prefer many small, cohesive files over one large file.
    • Group related types and functions into focused files (e.g. http_client.mbt, router.mbt).
    • If a file is getting large or unfocused, create a new file and move related declarations into it.
  2. You MAY freely move declarations between files inside the same package.
    • Each block is separated by
      ///|
      . Moving a function/struct/trait between files does not change semantics, as long as its name and pub-ness stay the same. The order of each block is irrelevant too.
    • It is safe to refactor by splitting or merging files inside a package.
  3. File names are purely organizational.
    • Do NOT assume file names define modules, and do NOT use file names in type paths.
    • Choose file names to describe a feature or responsibility, not to mirror type names rigidly.
  4. When adding new code:
    • Prefer adding it to an existing file that matches the feature.
    • If no good file exists, create a new file under the same package with a descriptive name.
    • Avoid creating giant "impl", “misc”, or “util” files.
  5. Tests:
    • Place tests in dedicated test files (e.g.
      *_test.mbt
      ) within the appropriate package. For a package (besides
      *_test.mbt
      files),
      *.mbt.md
      files are also blackbox test files in addition to Markdown files. The code blocks (separated by triple backticks)
      mbt check
      are treated as test cases and serve both purposes: documentation and tests. You may have
      README.mbt.md
      files with
      mbt check
      code examples. You can also symlink
      README.mbt.md
      to
      README.md
      to make it integrate better with GitHub.
    • It is fine — and encouraged — to have multiple small test files.
  6. Interface files (
    pkg.generated.mbti
    )
    pkg.generated.mbti
    files are compiler-generated summaries of each package's public API surface. They provide a formal, concise overview of all exported types, functions, and traits without implementation details. They are generated using
    moon info
    and useful for code review. When you have a commit that does not change public APIs,
    pkg.generated.mbti
    files will remain unchanged, so it is recommended to put
    pkg.generated.mbti
    in version control when you are done.
    For IDE navigation and symbol lookup commands, see the dedicated
    moon ide
    section below.
  1. 优先使用多个小而内聚的文件,而非单个大文件。
    • 将相关类型和函数分组到聚焦的文件中(例如http_client.mbt、router.mbt)。
    • 如果文件变得过大或不够聚焦,创建新文件并将相关声明移至其中。
  2. 你可以自由地在同一包内的文件之间移动声明。
    • 每个代码块由
      ///|
      分隔。只要函数/结构体/trait的名称和可见性保持不变,在文件之间移动它们不会改变语义。代码块的顺序也无关紧要。
    • 通过拆分或合并包内文件进行重构是安全的。
  3. 文件名仅用于组织。
    • 不要假设文件名定义了模块,也不要在类型路径中使用文件名。
    • 选择文件名来描述功能或职责,而非严格镜像类型名称。
  4. 添加新代码时:
    • 优先将其添加到匹配该功能的现有文件中。
    • 如果没有合适的文件,在同一包下创建一个具有描述性名称的新文件。
    • 避免创建庞大的“impl”、“misc”或“util”文件。
  5. 测试:
    • 将测试放在相应包内的专用测试文件中(例如
      *_test.mbt
      )。 对于包(除
      *_test.mbt
      文件外),
      *.mbt.md
      文件既是Markdown文档,也是黑盒测试文件。 由反引号分隔的代码块
      mbt check
      会被视为测试用例,兼具文档和测试功能。 你可以在
      README.mbt.md
      文件中包含
      mbt check
      代码示例。也可以将
      README.mbt.md
      软链接到
      README.md
      ,以更好地与GitHub集成。
    • 拥有多个小测试文件是没问题的,甚至是推荐的。
  6. 接口文件(
    pkg.generated.mbti
    pkg.generated.mbti
    文件是编译器生成的每个包的公共API摘要。 它们提供了所有导出类型、函数和trait的正式、简洁概述,不含实现细节。 可通过
    moon info
    生成,用于代码评审。当提交不改变公共API时,
    pkg.generated.mbti
    文件将保持不变,因此完成后建议将其纳入版本控制。
    有关IDE导航和符号查找命令,请参阅下面专门的
    moon ide
    部分。

Common Pitfalls to Avoid

需避免的常见陷阱

  • Don't use uppercase for variables/functions - compilation error
  • Don't forget
    mut
    for mutable record fields
    - immutable by default (note that Arrays typically do NOT need
    mut
    unless completely reassigning to the variable - simple push operations, for example, do not need
    mut
    )
  • Don't ignore error handling - errors must be explicitly handled
  • Don't use
    return
    unnecessarily
    - the last expression is the return value
  • Don't create methods without Type:: prefix - methods need explicit type prefix
  • Don't forget to handle array bounds - use
    get()
    for safe access
  • Don't forget @package prefix when calling functions from other packages
  • Don't use ++ or -- (not supported) - use
    i = i + 1
    or
    i += 1
  • Don't add explicit
    try
    for error-raising functions
    - errors propagate automatically (unlike Swift)
  • Legacy syntax: Legacy code may use
    function_name!(...)
    or
    function_name(...)?
    - these are deprecated, use normal calls.
  • Prefer range
    for
    loops over C-style
    -
    for i in 0..<(n-1) {...}
    and
    for j in 0..=6 {...}
    are more idiomatic in MoonBit
  • Async - MoonBit has no
    await
    keyword; do not add it. Async functions and tests are characterized by those which call other async functions. To identify a function or test as async, simply add the
    async
    prefix (e.g.
    [pub] async fn ...
    ,
    async test ...
    ).
  • 不要为变量/函数使用大写开头 - 会导致编译错误
  • 不要忘记为可变记录字段添加
    mut
    - 默认不可变(注意:Array通常不需要
    mut
    ,除非完全重新赋值给变量——例如简单的push操作不需要
    mut
  • 不要忽略错误处理 - 错误必须显式处理
  • 不要不必要地使用
    return
    - 最后一个表达式即为返回值
  • 不要创建不带Type::前缀的方法 - 方法需要显式类型前缀
  • 不要忘记处理数组边界 - 使用
    get()
    进行安全访问
  • 调用其他包的函数时不要忘记@package前缀
  • 不要使用++或--(不支持) - 使用
    i = i + 1
    i += 1
  • 不要为抛出错误的函数添加显式
    try
    - 错误会自动传播(与Swift不同)
  • 遗留语法:遗留代码可能使用
    function_name!(...)
    function_name(...)?
    - 这些已被弃用,请使用普通调用。
  • 优先使用范围
    for
    循环而非C风格循环
    -
    for i in 0..<(n-1) {...}
    for j in 0..=6 {...}
    在MoonBit中更符合惯用写法
  • 异步 - MoonBit没有
    await
    关键字;不要添加它。异步函数和测试的特征是调用其他异步函数。 要将函数或测试标识为异步,只需添加
    async
    前缀(例如
    [pub] async fn ...
    async test ...
    )。

moon
Essentials

moon
核心功能

Essential Commands

核心命令

  • moon new my_project
    - Create new project
  • moon run cmd/main
    - Run main package
  • moon run - < hello.mbt
    - Run code from stdin (useful for quick experiments)
  • moon run -e "code snippet"
    - Run code from command line argument (good for one-liners) Example:
    bash
    cat hello.mbt | moon run -
    This allows you to quickly test small snippets of MoonBit code without creating a full project. It can also be used with heredoc syntax for multi-line snippets:
    bash
    moon run - <<'EOF'
    fn main {
      println("Hello, MoonBit!")
    }
    EOF
    moon run -e 'fn main { println("Hello, MoonBit!") }'
  • moon build
    - Build project (
    moon run
    and
    moon build
    both support
    --target
    )
  • moon check
    - Type check without building, use it REGULARLY, it is fast (
    moon check
    also supports
    --target
    )
  • moon info
    - Type check and generate
    mbti
    files. Run it to see if any public interfaces changed. (
    moon info
    also supports
    --target
    .)
  • moon check --target all
    - Type check for all backends moon check --output-json can be used with
    jq
    to filter the output, e.g,
    moon check --output-json 2>&1 | jq -R 'fromjson? | select(.message |
        contains("unused"))'
    or, for richer post-processing, pipe into a small MoonBit program via
    moon run -e
    . Use
    --target native
    (the default
    wasm-gc
    does not support
    async fn main
    or
    @stdio.stdin
    ), a quoted heredoc (
    <<'EOF'
    ) so the shell does not expand
    $
    /backticks in the source, and a de-indented closing
    EOF
    :
    undefined
moon check --output-json 2>&1 | moon run --target native -e "$(cat <<'EOF' import { "moonbitlang/async", "moonbitlang/async/stdio", "moonbitlang/core/json", }
async fn main { let seen = {} while @stdio.stdin.read_until("\n") is Some(line) { try @json.parse(line.trim()) catch { _ => () } noraise { {"level": "warning", "path": String(p), ..} => if !seen.contains(p) { seen[p] = () println(p) } _ => () } } } EOF )"
Get the diagnostics with "unused" in the message, which can be used to find unused code.
- `moon explain` - Show built-in documentation for compiler diagnostics.
- `moon explain --diagnostics` lists warning mnemonics and IDs.
- `moon explain --diagnostics 31` explains warning 31 (`unused_optional_argument`).
- `moon explain --diagnostics unused_optional_argument` explains the same warning by mnemonic.
- `moon add package` - Add dependency
- `moon remove package` - Remove dependency
- `moon fmt` - Format code - should be run periodically - note that the files may be rewritten
Note you can also use `moon -C dir check` to run commands in a specific directory.
  • moon new my_project
    - 创建新项目
  • moon run cmd/main
    - 运行主包
  • moon run - < hello.mbt
    - 从标准输入运行代码(适用于快速实验)
  • moon run -e "code snippet"
    - 从命令行参数运行代码(适合单行代码) 示例:
    bash
    cat hello.mbt | moon run -
    这允许你快速测试MoonBit代码片段,无需创建完整项目。 也可结合heredoc语法用于多行片段:
    bash
    moon run - <<'EOF'
    fn main {
      println("Hello, MoonBit!")
    }
    EOF
    moon run -e 'fn main { println("Hello, MoonBit!") }'
  • moon build
    - 构建项目 (
    moon run
    moon build
    都支持
    --target
    参数)
  • moon check
    - 仅进行类型检查不构建,建议定期使用,速度很快 (
    moon check
    也支持
    --target
    参数)
  • moon info
    - 进行类型检查并生成
    mbti
    文件。 运行它查看公共接口是否变更。 (
    moon info
    也支持
    --target
    参数。)
  • moon check --target all
    - 为所有后端进行类型检查 moon check --output-json可与
    jq
    配合过滤输出,例如:
    moon check --output-json 2>&1 | jq -R 'fromjson? | select(.message |
        contains("unused"))'
    或者,为了更丰富的后处理,通过
    moon run -e
    将输出传入一个小型MoonBit程序:使用
    --target native
    (默认的
    wasm-gc
    不支持
    async fn main
    @stdio.stdin
    ),使用带引号的heredoc(
    <<'EOF'
    )避免shell展开源代码中的
    $
    /反引号,并使用无缩进的闭合
    EOF
    :
    undefined
moon check --output-json 2>&1 | moon run --target native -e "$(cat <<'EOF' import { "moonbitlang/async", "moonbitlang/async/stdio", "moonbitlang/core/json", }
async fn main { let seen = {} while @stdio.stdin.read_until("\n") is Some(line) { try @json.parse(line.trim()) catch { _ => () } noraise { {"level": "warning", "path": String(p), ..} => if !seen.contains(p) { seen[p] = () println(p) } _ => () } } } EOF )"
获取包含"unused"的诊断信息,可用于查找未使用的代码。
- `moon explain` - 显示编译器诊断的内置文档。
- `moon explain --diagnostics`列出警告助记符和ID。
- `moon explain --diagnostics 31`解释警告31(`unused_optional_argument`)。
- `moon explain --diagnostics unused_optional_argument`通过助记符解释同一警告。
- `moon add package` - 添加依赖
- `moon remove package` - 移除依赖
- `moon fmt` - 格式化代码 - 应定期运行 - 注意文件可能会被重写
注意你也可以使用`moon -C dir check`在特定目录中运行命令。

Test Commands

测试命令

  • moon test
    - Run all tests (
    moon test
    also supports
    --target
    )
  • moon test --update
    - Update snapshots
  • moon test -v
    - Verbose output with test names
  • moon test [dirname|filename]
    - Test specific directory or file
  • moon coverage analyze
    - Analyze coverage
  • moon test [dirname|filename] --filter 'glob'
    - Run tests matching filter
    moon test float/float_test.mbt --filter "Float::*"
    moon test float -F "Float::*" // shortcut syntax
  • moon test
    - 运行所有测试 (
    moon test
    也支持
    --target
    参数)
  • moon test --update
    - 更新快照
  • moon test -v
    - 显示详细输出及测试名称
  • moon test [dirname|filename]
    - 测试特定目录或文件
  • moon coverage analyze
    - 分析测试覆盖率
  • moon test [dirname|filename] --filter 'glob'
    - 运行匹配过滤器的测试
    moon test float/float_test.mbt --filter "Float::*"
    moon test float -F "Float::*" // 快捷语法

README.mbt.md
Generation Guide

README.mbt.md
生成指南

  • Output
    README.mbt.md
    in the package directory.
    *.mbt.md
    file and docstring contents treats
    mbt check
    specially.
    mbt check
    block will be included directly as code and also run by
    moon check
    and
    moon test
    . If you don't want the code snippets to be checked, explicit
    mbt nocheck
    is preferred. If you are only referencing types from the package, you should use
    mbt nocheck
    which will only be syntax highlighted. Symlink
    README.mbt.md
    to
    README.md
    to adapt to systems that expect
    README.md
    .
  • 在包目录中输出
    README.mbt.md
    *.mbt.md
    文件和文档字符串内容会特殊处理
    mbt check
    mbt check
    块会直接作为代码包含,同时被
    moon check
    moon test
    运行。如果不希望代码片段被检查,建议显式使用
    mbt nocheck
    。 如果仅引用包内的类型,应使用
    mbt nocheck
    ,仅进行语法高亮。 将
    README.mbt.md
    软链接到
    README.md
    ,以适配期望
    README.md
    的系统。

Testing Guide

测试指南

Use snapshot tests as it is easy to update when behavior changes.
  • Snapshot Tests:
    inspect(value, content="...")
    . If unknown, write
    inspect(value)
    and run
    moon test --update
    (or
    moon test -u
    ).
    • Use regular
      inspect()
      for simple values (uses
      Show
      trait)
    • Use
      @json.inspect()
      for complex nested structures (uses
      ToJson
      trait, produces more readable output)
    • It is encouraged to
      inspect
      or
      @json.inspect
      the whole return value of a function if the whole return value is not huge, this makes the test simple. You need
      impl (Show|ToJson) for YourType
      or
      derive (Show, ToJson)
      .
  • Update workflow: After changing code that affects output, run
    moon test --update
    to regenerate snapshots, then review the diffs in your test files (the
    content=
    parameter will be updated automatically).
  • Validation order: Follow the canonical sequence in
    Agent Workflow
    and
    Fast Task Playbooks
    .
  • Black-box by default: Call only public APIs via
    @package.fn
    . Use white-box tests only when private members matter.
  • Grouping: Combine related checks in one
    test "..." { ... }
    block for speed and clarity.
  • Panics: Name tests with prefix
    test "panic ..." {...}
    ; if the call returns a value, wrap it with
    ignore(...)
    to silence warnings.
  • Errors: Use
    try? f()
    to get
    Result[...]
    and
    inspect
    it when a function may raise.
使用快照测试,因为当行为变更时易于更新。
  • 快照测试:
    inspect(value, content="...")
    。如果未知内容,编写
    inspect(value)
    并运行
    moon test --update
    (或
    moon test -u
    )。
    • 简单值使用常规
      inspect()
      (使用
      Show
      trait)
    • 复杂嵌套结构使用
      @json.inspect()
      (使用
      ToJson
      trait,生成更易读的输出)
    • 如果函数的整个返回值不是很大,建议
      inspect
      @json.inspect
      整个返回值,这样测试更简单。你需要为自定义类型实现
      (Show|ToJson)
      或派生
      derive (Show, ToJson)
  • 更新流程: 修改影响输出的代码后,运行
    moon test --update
    重新生成快照,然后检查测试文件中的差异(
    content=
    参数会自动更新)。
  • 验证顺序: 遵循
    Agent工作流
    快速任务执行手册
    中的标准流程。
  • 默认使用黑盒测试:仅通过
    @package.fn
    调用公共API。仅当私有成员很重要时才使用白盒测试。
  • 分组:将相关检查合并到一个
    test "..." { ... }
    块中,以提高速度和清晰度。
  • 恐慌测试:测试名称以
    test "panic ..." {...}
    为前缀;如果调用返回值,使用
    ignore(...)
    包裹以消除警告。
  • 错误处理:当函数可能抛出错误时,使用
    try? f()
    获取
    Result[...]
    并进行
    inspect

Docstring tests

文档字符串测试

Public APIs are encouraged to have docstring tests.
mbt
///|
/// Get the largest element of a non-empty `Array`.
///
/// # Example
/// ```mbt check
/// test {
///   inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
/// }
/// ```
///
/// # Panics
/// Panics if the `xs` is empty.
pub fn sum_array(xs : Array[Int]) -> Int {
  xs.fold(init=0, (a, b) => a + b)
}
The MoonBit code in a docstring will be type checked and tested automatically (using
moon test --update
). In docstrings,
mbt check
should only contain
test
or
async test
.
鼓励为公共API添加文档字符串测试。
mbt
///|
/// 获取非空`Array`的最大元素。
///
/// # 示例
/// ```mbt check
/// test {
///   inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
/// }
/// ```
///
/// # 恐慌场景
/// 如果`xs`为空会触发恐慌。
pub fn sum_array(xs : Array[Int]) -> Int {
  xs.fold(init=0, (a, b) => a + b)
}
文档字符串中的MoonBit代码会被自动进行类型检查和测试(使用
moon test --update
)。在文档字符串中,
mbt check
块应仅包含
test
async test

Spec-driven Development

规范驱动开发

  • The spec can be written in a readonly
    spec.mbt
    file (name is conventional, not mandatory) with stub code marked as declarations:
mbt
///|
declare pub type Yaml

///|
declare pub fn Yaml::to_string(y : Yaml) -> String raise

///|
declare pub impl Eq for Yaml

///|
declare pub fn parse_yaml(s : String) -> Yaml raise
  • Add
    spec_easy_test.mbt
    ,
    spec_difficult_test.mbt
    , etc. to test the spec functions; everything will be type-checked(
    moon check
    ).
  • The AI or users can implement the
    declare
    functions in different files thanks to our package organization.
  • Run
    moon test
    to check everything is correct.
  • declare
    is supported for functions, methods, and types.
  • The
    pub type Yaml
    line is an intentionally opaque placeholder; the implementer chooses its representation.
  • Note the spec file can also contain normal code, not just declarations.
  • 规范可写在只读的
    spec.mbt
    文件中(名称是约定俗成的,非强制),其中存根代码标记为声明:
mbt
///|
declare pub type Yaml

///|
declare pub fn Yaml::to_string(y : Yaml) -> String raise

///|
declare pub impl Eq for Yaml

///|
declare pub fn parse_yaml(s : String) -> Yaml raise
  • 添加
    spec_easy_test.mbt
    spec_difficult_test.mbt
    等文件测试规范函数;所有内容都会被类型检查(
    moon check
    )。
  • 借助包组织,AI或用户可以在不同文件中实现
    declare
    函数。
  • 运行
    moon test
    检查所有内容是否正确。
  • declare
    支持函数、方法和类型。
  • pub type Yaml
    行是一个故意模糊的占位符;实现者可以选择其表示方式。
  • 注意规范文件也可以包含正常代码,不仅仅是声明。

moon ide [doc|peek-def|outline|find-references|hover|rename|analyze]
for code navigation and refactoring

moon ide [doc|peek-def|outline|find-references|hover|rename|analyze]
用于代码导航和重构

For project-local symbols and navigation, use:
  • moon ide doc <query>
    to discover available APIs, functions, types, and methods in MoonBit. Always prefer
    moon ide doc
    over other approaches when exploring what APIs are available, it is more powerful and accurate than
    grep_search
    or any regex-based searching tools.
  • moon ide outline .
    to scan a package,
  • moon ide find-references <symbol>
    to locate usages, and
  • moon ide peek-def
    for inline definition context and to locate toplevel symbols.
  • moon ide hover sym --loc filename:line:col
    to get type information at a specific location.
  • moon ide rename <symbol> <new_name> [--loc filename:line:col]
    to rename a symbol project-wide. Prefer
    --loc
    when symbol names are ambiguous.
  • moon ide analyze [path]
    to inspect public API usage of a package or module when planning safe refactors. These tools save tokens and are more precise than grepping (
    grep
    displays results in both definitions and call sites including comments too).
对于项目本地符号和导航,使用:
  • moon ide doc <query>
    发现MoonBit中可用的API、函数、类型和方法。探索可用API时,始终优先使用
    moon ide doc
    ,它比
    grep_search
    或任何基于正则的搜索工具更强大、更准确
  • moon ide outline .
    扫描包,
  • moon ide find-references <symbol>
    定位用法,
  • moon ide peek-def
    查看内联定义上下文并定位顶级符号。
  • moon ide hover sym --loc filename:line:col
    获取特定位置的类型信息。
  • moon ide rename <symbol> <new_name> [--loc filename:line:col]
    在项目范围内重命名符号。当符号名称模糊时,优先使用
    --loc
  • moon ide analyze [path]
    在规划安全重构时检查包或模块的公共API使用情况。 这些工具比grep更节省时间且更精确(
    grep
    会显示定义和调用站点的结果,包括注释)。

moon ide doc
for API Discovery

moon ide doc
用于API探索

moon ide doc
uses a specialized query syntax designed for symbol lookup:
  • Empty query:
    moon ide doc ''
    • In a module: shows all available packages in current module, including dependencies and moonbitlang/core
    • In a package: shows all symbols in current package
    • Outside package: shows all available packages
  • Function/value lookup:
    moon ide doc "[@pkg.]value_or_function_name"
  • Type lookup:
    moon ide doc "[@pkg.]Type_name"
    (builtin type does not need package prefix)
  • Method/field lookup:
    moon ide doc "[@pkg.]Type_name::method_or_field_name"
  • Package exploration:
    moon ide doc "@pkg"
    • Show package
      pkg
      and list all its exported symbols
    • Example:
      moon ide doc "@json"
      - explore entire
      @json
      package
    • Example:
      moon ide doc "@encoding/utf8"
      - explore nested package
  • Multiple queries:
    moon ide doc "query1" "query2" ...
    • Run multiple queries in one invocation and combine results
    • Example:
      moon ide doc "String" "Array" "@json"
      to explore multiple types and a package at once
  • Globbing: Use
    *
    wildcard for partial matches, e.g.
    moon ide doc "String::*rev*"
    to find all String methods with "rev" in their name
moon ide doc
使用专门的查询语法进行符号查找:
  • 空查询:
    moon ide doc ''
    • 在模块中:显示当前模块中所有可用包,包括依赖和moonbitlang/core
    • 在包中:显示当前包中的所有符号
    • 在包外:显示所有可用包
  • 函数/值查找:
    moon ide doc "[@pkg.]value_or_function_name"
  • 类型查找:
    moon ide doc "[@pkg.]Type_name"
    (内置类型不需要包前缀)
  • 方法/字段查找:
    moon ide doc "[@pkg.]Type_name::method_or_field_name"
  • 包探索:
    moon ide doc "@pkg"
    • 显示包
      pkg
      并列出其所有导出符号
    • 示例:
      moon ide doc "@json"
      - 探索整个
      @json
    • 示例:
      moon ide doc "@encoding/utf8"
      - 探索嵌套包
  • 多查询:
    moon ide doc "query1" "query2" ...
    • 一次运行多个查询并合并结果
    • 示例:
      moon ide doc "String" "Array" "@json"
      同时探索多个类型和一个包
  • 通配符: 使用
    *
    通配符进行部分匹配,例如
    moon ide doc "String::*rev*"
    查找名称中包含"rev"的所有String方法

moon ide doc
Examples

moon ide doc
示例

bash
undefined
bash
undefined

search for String methods in standard library:

搜索标准库中的String方法:

$ moon ide doc "String"
type String
pub fn String::add(String, String) -> String

... more methods omitted ...

$ moon ide doc "@buffer" # list all symbols in package buffer: moonbitlang/core/buffer
fn from_array(ArrayView[Byte]) -> Buffer
$ moon ide doc "String"
type String
pub fn String::add(String, String) -> String

... 省略更多方法 ...

$ moon ide doc "@buffer" # 列出buffer包中的所有符号: moonbitlang/core/buffer
fn from_array(ArrayView[Byte]) -> Buffer

... omitted ...

... 省略 ...

$ moon ide doc "@buffer.new" # list the specific function in a package: package "moonbitlang/core/buffer"
pub fn new(size_hint? : Int) -> Buffer Creates ... omitted ...
$ moon ide doc "String::rev" # globbing package "moonbitlang/core/string"
pub fn String::rev(String) -> String Returns ... omitted ...

... more

pub fn String::rev_find(String, StringView) -> Int? Returns ... omitted ...

**Best practice**: Treat this section as command reference; execution order is defined in `Agent Workflow`.
$ moon ide doc "@buffer.new" # 列出包中的特定函数: package "moonbitlang/core/buffer"
pub fn new(size_hint? : Int) -> Buffer 创建 ... 省略 ...
$ moon ide doc "String::rev" # 通配符 package "moonbitlang/core/string"
pub fn String::rev(String) -> String 返回 ... 省略 ...

... 更多

pub fn String::rev_find(String, StringView) -> Int? 返回 ... 省略 ...

**最佳实践**: 将此部分作为命令参考;执行顺序在`Agent工作流`中定义。

moon ide rename sym new_name [--loc filename:line:col]
example

moon ide rename sym new_name [--loc filename:line:col]
示例

When the user asks: "Can you rename the function
compute_sum
to
calculate_sum
?"
$ moon ide rename compute_sum calculate_sum --loc math_utils.mbt:2

*** Begin Patch
*** Update File: cmd/main/main.mbt
@@
 ///|
 fn main {
-  println(@math_utils.compute_sum(1, 2))
+  println(@math_utils.calculate_sum(1, 2))
 }
*** Update File: math_utils.mbt
@@
 ///|
-pub fn compute_sum(a: Int, b: Int) -> Int {
+pub fn calculate_sum(a: Int, b: Int) -> Int {
   a + b
 }
*** Update File: math_utils_test.mbt
@@
 ///|
 test {
-  inspect(@math_utils.compute_sum(1, 2))
+  inspect(@math_utils.calculate_sum(1, 2))
 }
*** End Patch
当用户询问:"你能将函数
compute_sum
重命名为
calculate_sum
吗?"
$ moon ide rename compute_sum calculate_sum --loc math_utils.mbt:2

*** Begin Patch
*** Update File: cmd/main/main.mbt
@@
 ///|
 fn main {
-  println(@math_utils.compute_sum(1, 2))
+  println(@math_utils.calculate_sum(1, 2))
 }
*** Update File: math_utils.mbt
@@
 ///|
-pub fn compute_sum(a: Int, b: Int) -> Int {
+pub fn calculate_sum(a: Int, b: Int) -> Int {
   a + b
 }
*** Update File: math_utils_test.mbt
@@
 ///|
 test {
-  inspect(@math_utils.compute_sum(1, 2))
+  inspect(@math_utils.calculate_sum(1, 2))
 }
*** End Patch

moon ide hover sym --loc filename:line:col
example

moon ide hover sym --loc filename:line:col
示例

When the user asks: "What is the signature and docstring of
filter
? at line 14 of hover.mbt"
$ moon ide hover  filter --loc hover.mbt:14
test {
  let a: Array[Int] = [1]
  inspect(a.filter((x) => {x > 1}))
            ^^^^^^
            ```moonbit
            fn[T] Array::filter(self : Array[T], f : (T) -> Bool raise?) -> Array[T] raise?
            ```
            ---

             Creates a new array containing all elements from the input array that satisfy
             ... omitted ...
}
当用户询问:"hover.mbt第14行的
filter
的签名和文档字符串是什么?"
$ moon ide hover  filter --loc hover.mbt:14
test {
  let a: Array[Int] = [1]
  inspect(a.filter((x) => {x > 1}))
            ^^^^^^
            ```moonbit
            fn[T] Array::filter(self : Array[T], f : (T) -> Bool raise?) -> Array[T] raise?
            ```
            ---

             创建一个新数组,包含输入数组中满足条件的所有元素
             ... 省略 ...
}

moon ide peek-def sym [--loc filename:line:col]
example

moon ide peek-def sym [--loc filename:line:col]
示例

When the user asks: "Can you check if
Parser::read_u32_leb128
is implemented correctly?" you can run
moon ide peek-def Parser::read_u32_leb128
to get the definition context (this is better than
grep
since it searches the whole project by semantics):
file
L45:|///|
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt raise ParseError {
L47:|  ...
...:| }
Now if you want to see the definition of the
Parser
struct, you can run:
bash
$ moon ide peek-def Parser --loc src/parse.mbt:46:4
Definition found at file src/parse.mbt
  | ///|
2 | priv struct Parser {
  |             ^^^^^^
  |   bytes : Bytes
  |   mut pos : Int
  | }
  |
For the
--loc
argument, the line number must be precise; the column can be approximate since the positional argument
Parser
helps locate the position.
If the "sym" is a toplevel symbol, the location can be omitted:
bash
$ moon ide peek-def String::rev
Found 1 symbols matching 'String::rev':

`pub fn String::rev` in package moonbitlang/core/builtin at /Users/usrname/.moon/lib/core/builtin/string_methods.mbt:1039-1044
1039 | ///|
     | /// Returns a new string with the characters in reverse order. It respects
     | /// Unicode characters and surrogate pairs but not grapheme clusters.
     | pub fn String::rev(self : String) -> String {
     |   self[:].rev()
     | }
当用户询问:"你能检查
Parser::read_u32_leb128
是否实现正确吗?" 你可以运行
moon ide peek-def Parser::read_u32_leb128
获取定义上下文 (这比
grep
更好,因为它通过语义搜索整个项目):
file
L45:|///|
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt raise ParseError {
L47:|  ...
...:| }
现在如果你想查看
Parser
结构体的定义,可以运行:
bash
$ moon ide peek-def Parser --loc src/parse.mbt:46:4
Definition found at file src/parse.mbt
  | ///|
2 | priv struct Parser {
  |             ^^^^^^
  |   bytes : Bytes
  |   mut pos : Int
  | }
  |
对于
--loc
参数,行号必须精确;列号可以近似,因为位置参数
Parser
有助于定位。
如果"sym"是顶级符号,可以省略位置:
bash
$ moon ide peek-def String::rev
Found 1 symbols matching 'String::rev':

`pub fn String::rev` in package moonbitlang/core/builtin at /Users/usrname/.moon/lib/core/builtin/string_methods.mbt:1039-1044
1039 | ///|
     | /// 返回字符顺序反转的新字符串。它支持Unicode字符和代理对,但不支持 grapheme clusters。
     | pub fn String::rev(self : String) -> String {
     |   self[:].rev()
     | }

moon ide outline [dir|file]
and
moon ide find-references <sym>
for Package Symbols

moon ide outline [dir|file]
moon ide find-references <sym>
用于包符号

Use
moon ide outline
to scan a package or file for top-level symbols and locate usages without grepping.
  • moon ide outline dir
    outlines the current package directory (per-file headers)
  • moon ide outline parser.mbt
    outlines a single file This is useful when you need a quick inventory of a package, or to find the right file before
    peek-def
    .
  • moon ide find-references TranslationUnit
    finds all references to a symbol in the current module
bash
$ moon ide outline .
spec.mbt:
 L003 | pub(all) enum CStandard {
        ...
 L013 | pub(all) struct Position {
        ...
bash
$ moon ide find-references TranslationUnit
使用
moon ide outline
扫描包或文件的顶级符号,无需grep即可定位用法。
  • moon ide outline dir
    列出当前包目录的大纲(按文件头部)
  • moon ide outline parser.mbt
    列出单个文件的大纲 当你需要快速了解包的内容,或在
    peek-def
    前找到正确文件时,这很有用。
  • moon ide find-references TranslationUnit
    查找当前模块中符号的所有引用
bash
$ moon ide outline .
spec.mbt:
 L003 | pub(all) enum CStandard {
        ...
 L013 | pub(all) struct Position {
        ...
bash
$ moon ide find-references TranslationUnit

Package Management

包管理

Adding Dependencies

添加依赖

sh
moon add moonbitlang/x        # Add latest version
moon add moonbitlang/x@0.4.6  # Add specific version
sh
moon add moonbitlang/x        # 添加最新版本
moon add moonbitlang/x@0.4.6  # 添加特定版本

Updating Dependencies

更新依赖

sh
moon update                   # Update package index
sh
moon update                   # 更新包索引

Browsing Third-Party Source (
moon fetch
)

浏览第三方源代码(
moon fetch

moon fetch <author>/<module>[@<version>]
downloads a package's source into
.repos/<author>/<module>/<version>/
for offline reading (examples, internals, generated
.mbti
). It does NOT add the package to
moon.mod.json
— use
moon add
for that. Add
.repos/
to
.gitignore
.
sh
moon fetch moonbitlang/async@0.18.1   # browse source/examples without taking a dependency
moon fetch <author>/<module>[@<version>]
将包的源代码下载到
.repos/<author>/<module>/<version>/
用于离线阅读(示例、内部实现、生成的
.mbti
)。它不会将包添加到
moon.mod.json
——使用
moon add
来添加依赖。将
.repos/
添加到
.gitignore
sh
moon fetch moonbitlang/async@0.18.1   # 不添加依赖即可浏览源代码/示例

Typical Module configurations (
moon.mod.json
)

典型模块配置(
moon.mod.json

json
{
  "name": "username/hello", // Required format for published modules
  "version": "0.1.0",
  "source": ".", // Source directory(optional, default: ".")
  "repository": "", // Git repository URL
  "keywords": [], // Search keywords
  "description": "...", // Module description
  "deps": {
    // Dependencies from mooncakes.io, using`moon add` to add dependencies
    "moonbitlang/x": "0.4.6"
  }
}
json
{
  "name": "username/hello", // 发布模块的必填格式
  "version": "0.1.0",
  "source": ".", // 源目录(可选,默认:".")
  "repository": "", // Git仓库URL
  "keywords": [], // 搜索关键词
  "description": "...", // 模块描述
  "deps": {
    // 来自mooncakes.io的依赖,使用`moon add`添加
    "moonbitlang/x": "0.4.6"
  }
}

Typical Package configuration (
moon.pkg
)

典型包配置(
moon.pkg

moon.pkg for simplicity
import {
  "username/hello/liba",
  "moonbitlang/x/encoding" @libb
}
import {...} for "test"
import {...} for "wbtest"
options("is-main" : true) // other options
Packages are per directory and packages without a
moon.pkg
file are not recognized.
简化版moon.pkg
import {
  "username/hello/liba",
  "moonbitlang/x/encoding" @libb
}
import {...} for "test"
import {...} for "wbtest"
options("is-main" : true) // 其他选项
每个目录对应一个包,没有
moon.pkg
文件的包不会被识别。

Package Importing (used in moon.pkg)

包导入(用于moon.pkg)

  • Import format:
    "module_name/package_path"
  • Usage:
    @alias.function()
    to call imported functions
  • Default alias: Last part of path (e.g.,
    liba
    for
    username/hello/liba
    )
  • Package reference: Use
    @packagename
    in test files to reference the tested package
Package Alias Rules:
  • Import
    "username/hello/liba"
    → use
    @liba.function()
    (default alias is the last path segment)
  • Import with custom alias
    import { "moonbitlang/x/encoding" @enc}
    → use
    @enc.function()
    (Note that this is unnecessary when the last path segment is identical to the alias name.)
  • In
    _test.mbt
    or
    _wbtest.mbt
    files, the package being tested is auto-imported
Example:
mbt
///|
/// In main.mbt after importing "username/hello/liba" in `moon.pkg`
fn main {
  println(@liba.hello()) // Calls hello() from liba package
}
  • 导入格式:
    "module_name/package_path"
  • 用法: 使用
    @alias.function()
    调用导入的函数
  • 默认别名: 路径的最后一部分(例如
    username/hello/liba
    的默认别名是
    liba
  • 包引用: 在测试文件中使用
    @packagename
    引用被测试的包
包别名规则:
  • 导入
    "username/hello/liba"
    → 使用
    @liba.function()
    (默认别名是路径的最后一段)
  • 使用自定义别名导入
    import { "moonbitlang/x/encoding" @enc}
    → 使用
    @enc.function()
    (注意:当路径最后一段与别名相同时,此操作不必要。)
  • _test.mbt
    _wbtest.mbt
    文件中,被测试的包会被自动导入
示例:
mbt
///|
/// 在`moon.pkg`中导入"username/hello/liba"后,在main.mbt中
fn main {
  println(@liba.hello()) // 调用liba包中的hello()
}

Using the Standard Library (moonbitlang/core)

使用标准库(moonbitlang/core)

MoonBit standard library (moonbitlang/core) packages were automatically imported. MoonBit is transitioning to explicit imports—you will see a warning to add imports like
moonbitlang/core/strconv
to
moon.pkg
if you use them. The module is always available without adding to dependencies.
MoonBit标准库(moonbitlang/core)包会被自动导入。MoonBit正过渡到显式导入——如果你使用了
moonbitlang/core/strconv
等包,会收到警告提示你将其添加到
moon.pkg
中。 该模块始终可用,无需添加到依赖中。

Creating Packages

创建包

To add a new package
fib
under
.
:
  1. Create directory:
    ./fib/
  2. Add
    ./fib/moon.pkg
  3. Add
    .mbt
    files with your code
  4. Import in dependent packages:
      import {
       "username/hello/fib",
      }
For more advanced topics like
conditional compilation
,
link configuration
,
warning control
, and
pre-build commands
, see
references/advanced-moonbit-build.md
.
要在
.
下添加新包
fib
  1. 创建目录:
    ./fib/
  2. 添加
    ./fib/moon.pkg
  3. 添加包含代码的
    .mbt
    文件
  4. 在依赖包中导入:
      import {
       "username/hello/fib",
      }
有关
条件编译
链接配置
警告控制
预构建命令
等更高级主题,请参阅
references/advanced-moonbit-build.md

Async IO

异步IO

Asynchronous programming uses compiler support plus the
moonbitlang/async
runtime. The runtime supports the native backend best, has limited JavaScript support for IO-independent APIs, and does not support WebAssembly yet. For async IO examples, prefer native. Use
moon add moonbitlang/async@<version>
and
moon ide doc "@async"
to explore the API.
User-facing subpackages:
@async
(core: tasks, timers, cancellation),
@async/aqueue
,
@async/fs, 
@async/stdio
, 
@async/websocket
, ..etc. Each must be imported separately in 
moon.pkg`.
  1. Add the dependency and pin the native target in
    moon.mod.json
    :
    json
    {
      "deps": { "moonbitlang/async": "0.18.1" },
      "preferred-target": "native"
    }
  2. In the executable's
    moon.pkg
    , set
    is-main
    , restrict to native, and import what you need:
    import {
      "moonbitlang/async",
      "moonbitlang/async/stdio",
    }
    supported_targets = "+native"
    options("is-main": true)
  3. Define
    async fn main
    (not
    fn main
    ). Spawn concurrent tasks via
    with_task_group
    for structured concurrency:
    mbt
    ///|
    async fn main {
      @async.with_task_group(group => {
        group.spawn_bg(() => {
          @async.sleep(50)
          @stdio.stdout.write("A\n")
        })
        group.spawn_bg(() => {
          @async.sleep(20)
          @stdio.stdout.write("B\n")
        })
      })
    }
Structured-concurrency contract for
with_task_group
:
  • When
    with_task_group
    returns, every task spawned in the group is guaranteed to have terminated — no orphan tasks, no resource leaks.
  • If any spawned task fails (and was spawned without
    allow_failure=true
    ), the whole group fails: every other task in the group is cancelled, and the error propagates out of
    with_task_group
    .
  • Cancelled tasks are not considered failures; they raise a cancellation error but don't trigger peer cancellation.
Closure syntax for
spawn_bg
/
spawn
:
  • () => { ... }
    — idiomatic; async-ness is inferred from context.
  • async fn() { ... }
    — explicit annotation; equivalent to the arrow form.
  • ⚠️
    fn() { ... }
    — triggers
    Warning [0027] deprecated_syntax
    : "this
    fn
    is asynchronous but not annotated with
    async
    ". Don't use.
  • async () => ...
    ,
    fn() async { ... }
    ,
    fn(args) async { ... }
    — all parse errors.
    async
    only goes before
    fn
    , never before an arrow lambda or after a parameter list.
异步编程使用编译器支持加上
moonbitlang/async
运行时。该运行时对native后端支持最佳,对IO无关API的JavaScript支持有限,目前不支持WebAssembly。异步IO示例优先使用native。使用
moon add moonbitlang/async@<version>
moon ide doc "@async"
探索API。
面向用户的子包:
@async
(核心:任务、定时器、取消)、
@async/aqueue
@async/fs
@async/stdio
@async/websocket
等。 每个都必须在
moon.pkg
中单独导入。
  1. moon.mod.json
    中添加依赖并指定native目标:
    json
    {
      "deps": { "moonbitlang/async": "0.18.1" },
      "preferred-target": "native"
    }
  2. 在可执行文件的
    moon.pkg
    中,设置
    is-main
    ,限制为native,并导入所需内容:
    import {
      "moonbitlang/async",
      "moonbitlang/async/stdio",
    }
    supported_targets = "+native"
    options("is-main": true)
  3. 定义
    async fn main
    (而非
    fn main
    )。通过
    with_task_group
    生成并发任务以实现结构化并发:
    mbt
    ///|
    async fn main {
      @async.with_task_group(group => {
        group.spawn_bg(() => {
          @async.sleep(50)
          @stdio.stdout.write("A\n")
        })
        group.spawn_bg(() => {
          @async.sleep(20)
          @stdio.stdout.write("B\n")
        })
      })
    }
with_task_group
的结构化并发契约
:
  • with_task_group
    返回时,组中生成的每个任务都保证已终止——无孤儿任务,无资源泄漏。
  • 如果任何生成的任务失败(且生成时未设置
    allow_failure=true
    ),整个组都会失败:组中其他所有任务都会被取消,错误会传播出
    with_task_group
  • 被取消的任务不被视为失败;它们会抛出取消错误,但不会触发对等任务取消。
spawn_bg
/
spawn
的闭包语法
:
  • () => { ... }
    — 符合惯用写法;异步性由上下文推断。
  • async fn() { ... }
    — 显式注解;与箭头形式等效。
  • ⚠️
    fn() { ... }
    — 触发
    Warning [0027] deprecated_syntax
    : "此
    fn
    是异步的但未标注
    async
    "。请勿使用。
  • async () => ...
    ,
    fn() async { ... }
    ,
    fn(args) async { ... }
    — 均为解析错误。
    async
    只能放在
    fn
    之前,不能放在箭头lambda之前或参数列表之后。

Async tests

异步测试

Use
async test
for tests that call async functions. The package containing the test must import
moonbitlang/async
for the test mode; import any async subpackages used by the test in the same
for "test"
block.
import {
  "moonbitlang/async",
  "moonbitlang/async/stdio",
} for "test"
mbt
///|
async test "sleep completes" {
  @async.sleep(1)
  inspect("done", content="done")
}
  • There is no
    await
    keyword (similar to functions that raise errors). Inside an
    async test
    , call async functions normally.
  • Async tests run in parallel by default. Avoid shared ports, files, environment variables, and global mutable state unless each test isolates its resources.
  • Run with
    moon test --target native
    unless
    moon.mod.json
    sets
    "preferred-target": "native"
    . Use
    moon test -v
    when checking test names or async scheduling behavior.
  • In
    README.mbt.md
    and docstrings,
    mbt check
    blocks may contain
    async test
    blocks; make sure the package imports
    moonbitlang/async
    for the relevant test mode.
对于调用异步函数的测试,使用
async test
。包含测试的包必须为测试模式导入
moonbitlang/async
;在同一个
for "test"
块中导入测试使用的任何异步子包。
import {
  "moonbitlang/async",
  "moonbitlang/async/stdio",
} for "test"
mbt
///|
async test "sleep completes" {
  @async.sleep(1)
  inspect("done", content="done")
}
  • 没有
    await
    关键字(类似抛出错误的函数)。在
    async test
    中,正常调用异步函数即可。
  • 异步测试默认并行运行。除非每个测试隔离其资源,否则避免共享端口、文件、环境变量和全局可变状态。
  • 运行时使用
    moon test --target native
    ,除非
    moon.mod.json
    设置了
    "preferred-target": "native"
    。检查测试名称或异步调度行为时使用
    moon test -v
  • README.mbt.md
    和文档字符串中,
    mbt check
    块可包含
    async test
    块;确保包为相关测试模式导入了
    moonbitlang/async

MoonBit Language Tour

MoonBit语言概览

Core facts

核心特性

  • Expression‑oriented:
    if
    ,
    match
    , loops return values; the last expression is the return value.
  • References by default: Arrays/Maps/structs mutate via reference; use
    Ref[T]
    for primitive mutability.
  • Blocks: Separate top‑level items with
    ///|
    . Generate code block‑by‑block. If a blank line is desired within a block (enclosed by curly braces), add a comment line after the blank line (with or without comment text).
  • Visibility:
    fn
    is private by default;
    pub
    exposes read/construct as allowed;
    pub(all)
    allows external construction.
  • Naming convention: lower_snake for values/functions; UpperCamel for types/enums; enum variants start UpperCamel.
  • Packages: No
    import
    in code files; call via
    @alias.fn
    . Configure imports in
    moon.pkg
    .
  • Placeholders:
    ...
    is a valid placeholder in MoonBit code for incomplete implementations.
  • Global values: immutable by default and generally require type annotations.
  • Garbage collection: MoonBit has a GC, there is no lifetime annotation, there's no ownership system. Unlike Rust, like F#,
    let mut
    is only needed when you want to reassign a variable, not for mutating fields of a struct or elements of an array/map.
  • 表达式导向:
    if
    match
    、循环都返回值;最后一个表达式即为返回值。
  • 默认引用: Array/Map/结构体通过引用可变;使用
    Ref[T]
    实现基本类型可变。
  • 代码块: 使用
    ///|
    分隔顶级项。逐块生成代码。 如果需要在代码块(由大括号包裹)内添加空行,在空行后添加注释行(可带或不带注释文本)。
  • 可见性:
    fn
    默认私有;
    pub
    允许按规则读取/构造;
    pub(all)
    允许外部构造。
  • 命名约定: 值/函数使用小写蛇形命名;类型/枚举使用大驼峰命名;枚举变体以大驼峰开头。
  • : 代码文件中无
    import
    ;通过
    @alias.fn
    调用。在
    moon.pkg
    中配置导入。
  • 占位符:
    ...
    是MoonBit代码中有效的未实现占位符。
  • 全局值: 默认不可变,通常需要类型注解。
  • 垃圾回收: MoonBit有GC,无需生命周期注解,无所有权系统。 与Rust不同,类似F#,
    let mut
    仅在你想重新赋值变量时需要,而不是为了修改结构体字段或数组/Map的元素。

MoonBit Error Handling (Checked Errors)

MoonBit错误处理(检查型错误)

MoonBit uses checked error-throwing functions, not unchecked exceptions. All errors are a subtype of
Error
and you can declare your own error types using
suberror
. Use
raise
in signatures to declare error types and let errors propagate by default.
try { } catch { }
to handle errors explicitly. Use
try!
to abort if it does raise. Occasionally, use
try?
to convert to
Result[...]
in tests for inspection.
mbt
///|
/// Declare error types with 'suberror'
suberror ValueError {
  ValueError(String)
}

///|
/// Tuple struct to hold position info
struct Position(Int, Int) derive(ToJson, Debug, Eq)

///|
/// ParseError is subtype of Error
pub(all) suberror ParseError {
  InvalidChar(pos~ : Position, Char) // pos is labeled
  InvalidEof(pos~ : Position)
  InvalidNumber(pos~ : Position, String)
  InvalidIdentEscape(pos~ : Position)
} derive(Eq, ToJson, Debug)

///|
/// Functions declare what they can throw
fn parse_int(s : String, position~ : Position) -> Int raise ParseError {
  // 'raise' throws an error
  if s is "" {
    raise ParseError::InvalidEof(pos=position)
  }
  ... // parsing logic
}

///|
/// Just declare `raise` to not track specific error types
fn div(x : Int, y : Int) -> Int raise {
  if y is 0 {
    fail("Division by zero")
  }
  x / y
}

///|
test "inspect raise function" {
  let result : Result[Int, Error] = try? div(1, 0)
  guard result is Err(Failure::Failure(msg)) && msg.contains("Division by zero") else {
    fail("Expected error")
  }
}

// Three ways to handle errors:

///|
/// Propagate automatically
fn use_parse(s : String, position~ : Position) -> Int raise ParseError {
  let x = parse_int(s, position~) // label punning, equivalent to position=position
  // Error auto-propagates by default.
  // Unlike Swift, you do not need to mark `try` for functions that can raise
  // errors; the compiler infers it automatically. This keeps error handling
  // explicit but concise.
  x * 2
}

///|
/// Use try! to abort if it raises, no raise in the signature
fn use_parse2(position~ : Position) -> Int {
  let x = try! parse_int("123", position~) // label punning
  x * 2
}

///|
/// Handle with try-catch
fn handle_parse(s : String, position~ : Position) -> Int {
  parse_int(s, position~) catch {
    ParseError::InvalidEof(pos=_) => {
      println("Parse failed: InvalidEof")
      -1 // Default value
    }
    _ => 2
  }
}
Important: When calling a function that can raise errors, if you only want to propagate the error, you do not need any marker; the compiler infers it. Note that all
async
functions automatically can raise errors without explicitly stating this.
MoonBit使用检查型抛错函数,而非未检查异常。所有错误都是
Error
的子类型,你可以使用
suberror
声明自己的错误类型。 在签名中使用
raise
声明错误类型,默认让错误传播。使用
try { } catch { }
显式处理错误。使用
try!
在出错时中止。偶尔在测试中使用
try?
转换为
Result[...]
进行检查。
mbt
///|
/// 使用'suberror'声明错误类型
suberror ValueError {
  ValueError(String)
}

///|
/// 保存位置信息的元组结构体
struct Position(Int, Int) derive(ToJson, Debug, Eq)

///|
/// ParseError是Error的子类型
pub(all) suberror ParseError {
  InvalidChar(pos~ : Position, Char) // pos是带标签的参数
  InvalidEof(pos~ : Position)
  InvalidNumber(pos~ : Position, String)
  InvalidIdentEscape(pos~ : Position)
} derive(Eq, ToJson, Debug)

///|
/// 函数声明可能抛出的错误
fn parse_int(s : String, position~ : Position) -> Int raise ParseError {
  // 'raise'抛出错误
  if s is "" {
    raise ParseError::InvalidEof(pos=position)
  }
  ... // 解析逻辑
}

///|
/// 仅声明`raise`不跟踪具体错误类型
fn div(x : Int, y : Int) -> Int raise {
  if y is 0 {
    fail("Division by zero")
  }
  x / y
}

///|
test "检查抛错函数" {
  let result : Result[Int, Error] = try? div(1, 0)
  guard result is Err(Failure::Failure(msg)) && msg.contains("Division by zero") else {
    fail("Expected error")
  }
}

// 三种错误处理方式:

///|
/// 自动传播错误
fn use_parse(s : String, position~ : Position) -> Int raise ParseError {
  let x = parse_int(s, position~) // 标签简写,等效于position=position
  // 错误默认自动传播。
  // 与Swift不同,你不需要为可能抛错的函数标记`try`;编译器会自动推断。这使错误处理既显式又简洁。
  x * 2
}

///|
/// 使用try!在出错时中止,签名中无raise
fn use_parse2(position~ : Position) -> Int {
  let x = try! parse_int("123", position~) // 标签简写
  x * 2
}

///|
/// 使用try-catch处理错误
fn handle_parse(s : String, position~ : Position) -> Int {
  parse_int(s, position~) catch {
    ParseError::InvalidEof(pos=_) => {
      println("Parse failed: InvalidEof")
      -1 // 默认值
    }
    _ => 2
  }
}
重要提示:调用可能抛错的函数时,如果你只想传播错误,不需要任何标记;编译器会自动推断。 注意所有
async
函数默认可以抛错,无需显式声明。

Integer, Char and overloaded literals

整数、字符和重载字面量

MoonBit supports
Byte
,
Int16
,
Int
,
UInt16
,
UInt
,
Int64
,
UInt64
, etc. When the type is known, the literal can be overloaded:
mbt
///|
test "integer and char literal overloading disambiguation via type in the current context" {
  let (int, uint, uint16, int64, byte) : (Int, UInt, UInt16, Int64, Byte) = (
    1, 1, 1, 1, 1,
  )
  // The literal `1` is overloaded based on the expected type in the current context.
  // compile time error if the literal cannot be represented in the target type,
  // e.g. let a7 : Byte = 256 // ❌ won't compile, 256 exceeds Byte max value 255
  assert_eq(int, uint16.to_int())
  let (a1, a2, a3) : (Int, Char, UInt16) = ('b', 'b', 'b')
  // char literal overloading, `a1` will be the unicode value of 'b',
  // compile time error when the literal cannot be represented in the target type
  // e.g, let a6 : UInt16 = '𐍈' // ❌ won't compile, '𐍈' is U+10348, which exceeds UInt16 max value 0xffff
  let a4 : Byte = b'b' // Byte literal
}
MoonBit支持
Byte
Int16
Int
UInt16
UInt
Int64
UInt64
等类型。 当类型已知时,字面量可以重载:
mbt
///|
test "整数和字符字面量通过上下文类型消除歧义" {
  let (int, uint, uint16, int64, byte) : (Int, UInt, UInt16, Int64, Byte) = (
    1, 1, 1, 1, 1,
  )
  // 字面量`1`根据当前上下文的预期类型重载。
  // 如果字面量无法在目标类型中表示,会触发编译错误,
  // 例如let a7 : Byte = 256 // ❌ 无法编译,256超过Byte的最大值255
  assert_eq(int, uint16.to_int())
  let (a1, a2, a3) : (Int, Char, UInt16) = ('b', 'b', 'b')
  // 字符字面量重载,`a1`将是'b'的unicode值,
  // 当字面量无法在目标类型中表示时触发编译错误
  // 例如let a6 : UInt16 = '𐍈' // ❌ 无法编译,'𐍈'是U+10348,超过UInt16的最大值0xffff
  let a4 : Byte = b'b' // Byte字面量
}

Bytes (Immutable)

Bytes(不可变)

mbt
///|
test "bytes literals" {
  let b0 : Bytes = b"abcd"
  let b1 : Bytes = [0xff, 0x00, 0x01] // Array literal overloading
  guard b0 is [b'a', ..] && b0[1] is b'b' else {
    // Bytes can be pattern matched as BytesView and indexed
    fail("unexpected bytes content")
  }
}
mbt
///|
test "bytes字面量" {
  let b0 : Bytes = b"abcd"
  let b1 : Bytes = [0xff, 0x00, 0x01] // 数组字面量重载
  guard b0 is [b'a', ..] && b0[1] is b'b' else {
    // Bytes可以作为BytesView进行模式匹配和索引
    fail("unexpected bytes content")
  }
}

Array (Resizable)

Array(可调整大小)

mbt
///|
test "array literals overloading: disambiguation via type in the current context" {
  let (a0, a1, a2, a3) : (
    Array[Int],
    FixedArray[Int],
    ReadOnlyArray[Int],
    ArrayView[Int],
  ) = ([1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3])
  // The literal `[1, 2, 3]` is overloaded based on the expected type in the current context.
  // Defaults to Array[_]
}
mbt
///|
test "数组字面量重载:通过上下文类型消除歧义" {
  let (a0, a1, a2, a3) : (
    Array[Int],
    FixedArray[Int],
    ReadOnlyArray[Int],
    ArrayView[Int],
  ) = ([1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3])
  // 字面量`[1, 2, 3]`根据当前上下文的预期类型重载。
  // 默认是Array[_]
}

String (Immutable UTF-16)

String(不可变UTF-16)

s[i]
returns a code unit (UInt16),
s.get_char(i)
returns
Char?
. Since MoonBit supports char literal overloading, you can write code snippets like this:
mbt
///|
test "string indexing and utf8 encode/decode" {
  let s = "hello world"
  let b0 : UInt16 = s[0]
  guard b0 is ('\n' | 'h' | 'b' | 'a'..='z') && s is [.. "hello", .. rest] else {
    fail("unexpected string content")
  }
  guard rest is " world"  // otherwise will crash (guard without else)

  // In check mode (expression with explicit type), ('\n' : UInt16) is valid.

  // Using get_char for Option handling
  let b1 : Char? = s.get_char(0)
  assert_true(b1 is Some('a'..='z'))

  // ⚠️ Important: Variables won't work with direct indexing
  let eq_char : Char = '='
  // s[0] == eq_char // ❌ Won't compile - eq_char is not a literal, lhs is UInt while rhs is Char
  // Use: s[0] == '=' or s.get_char(0) == Some(eq_char)
  let bytes = @utf8.encode("中文") // utf8 encode package is in stdlib
  assert_true(bytes is [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87])
  let s2 : String = @utf8.decode(bytes) // decode utf8 bytes back to String
  assert_true(s2 is "中文")
  for c in "中文" {
    let _ : Char = c // unicode safe iteration
    println("char: \{c}") // iterate over chars
  }
}
s[i]
返回代码单元(UInt16),
s.get_char(i)
返回
Char?
。 由于MoonBit支持字符字面量重载,你可以编写如下代码片段:
mbt
///|
test "字符串索引和utf8编码/解码" {
  let s = "hello world"
  let b0 : UInt16 = s[0]
  guard b0 is ('\n' | 'h' | 'b' | 'a'..='z') && s is [.. "hello", .. rest] else {
    fail("unexpected string content")
  }
  guard rest is " world"  // 否则会崩溃(guard无else分支)

  // 在检查模式(带显式类型的表达式)中,('\n' : UInt16)是有效的。

  // 使用get_char处理Option
  let b1 : Char? = s.get_char(0)
  assert_true(b1 is Some('a'..='z'))

  // ⚠️ 重要提示:变量不能直接用于索引
  let eq_char : Char = '='
  // s[0] == eq_char // ❌ 无法编译 - eq_char不是字面量,左边是UInt右边是Char
  // 正确写法:s[0] == '=' 或 s.get_char(0) == Some(eq_char)
  let bytes = @utf8.encode("中文") // utf8编码包在标准库中
  assert_true(bytes is [0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87])
  let s2 : String = @utf8.decode(bytes) // 将utf8字节解码回String
  assert_true(s2 is "中文")
  for c in "中文" {
    let _ : Char = c // 安全的unicode迭代
    println("char: \{c}") // 遍历字符
  }
}

String Interpolation && StringBuilder

字符串插值 && StringBuilder

MoonBit uses
\{}
for string interpolation, for custom types, they need to implement trait
Show
.
mbt
///|
test "string interpolation basics" {
  let name : String = "Moon"
  let config = { "cache": 123 }
  let version = 1.0
  println("Hello \{name} v\{version}") // "Hello Moon v1"
  // ❌ Wrong - quotes inside interpolation not allowed:
  // println("  - Checking if 'cache' section exists: \{config["cache"]}")

  // ✅ Correct - extract to variable first:
  let has_key = config["cache"] // `"` not allowed in interpolation
  println("  - Checking if 'cache' section exists: \{has_key}")
  let sb = StringBuilder()
  sb.write_char('[')
  sb.write_view([ for x in [1, 2, 3] => "\{x}" ].join(","))
  sb.write_char(']')
  inspect(sb, content="[1,2,3]")
  let x = 42
  let streamed = StringBuilder()
  streamed <+ "hello \{x}"
  inspect(streamed, content="hello 42")
}
Expressions inside
\{}
can only be basic expressions (no quotes, newlines, or nested interpolations). String literals are not allowed as they make lexing too difficult.
String interpolation can also be streamed directly into a
Logger
/
StringBuilder
-style writer with
<+
:
mbt
writer <+ "hello \{x}"
This expands to calls on the writer:
mbt
writer.write_string("hello ")
writer.write(x)
Literal string segments use
write_string
; interpolated expressions use
write
. The expansion is macro-style: it depends on how the
writer
type implements the
write_string
and
write
methods. Types such as HTMLBuilder or JSONBuilder can support interpolation and streaming with the same syntax but different semantics. Because MoonBit allows local methods on foreign types, a package can adapt an existing writer type to this syntax by adding local
write_string
and
write
methods.
MoonBit使用
\{}
进行字符串插值,自定义类型需要实现
Show
trait。
mbt
///|
test "字符串插值基础" {
  let name : String = "Moon"
  let config = { "cache": 123 }
  let version = 1.0
  println("Hello \{name} v\{version}") // "Hello Moon v1"
  // ❌ 错误 - 插值内不允许引号:
  // println("  - Checking if 'cache' section exists: \{config["cache"]}")

  // ✅ 正确 - 先提取到变量:
  let has_key = config["cache"] // 插值内不允许`"`
  println("  - Checking if 'cache' section exists: \{has_key}")
  let sb = StringBuilder()
  sb.write_char('[')
  sb.write_view([ for x in [1, 2, 3] => "\{x}" ].join(","))
  sb.write_char(']')
  inspect(sb, content="[1,2,3]")
  let x = 42
  let streamed = StringBuilder()
  streamed <+ "hello \{x}"
  inspect(streamed, content="hello 42")
}
\{}
内的表达式只能是基本表达式(无引号、换行或嵌套插值)。不允许字符串字面量,因为这会使词法分析过于复杂。
字符串插值也可以通过
<+
直接流式写入
Logger
/
StringBuilder
风格的写入器:
mbt
writer <+ "hello \{x}"
这会展开为对写入器的调用:
mbt
writer.write_string("hello ")
writer.write(x)
字面字符串段使用
write_string
;插值表达式使用
write
。 展开是宏风格的:取决于
writer
类型如何实现
write_string
write
方法。HTMLBuilder或JSONBuilder等类型可以支持相同语法但不同语义的插值和流式写入。 由于MoonBit允许在外部类型上定义本地方法,包可以通过添加本地
write_string
write
方法来适配现有写入器类型。

Multiple line strings

多行字符串

mbt
///|
test "multi-line string literals" {
  let multi_line_string : String =
    #|Hello "world"
    #|World
    #|
  let multi_line_string_with_interp : String =
    $|Line 1 ""
    $|Line 2 \{1+2}
    $|
  // no escape in `#|`,
  // only escape '\{..}` in `$|`
  assert_eq(multi_line_string, "Hello \"world\"\nWorld\n")
  assert_eq(multi_line_string_with_interp, "Line 1 \"\"\nLine 2 3\n")
}
mbt
///|
test "多行字符串字面量" {
  let multi_line_string : String =
    #|Hello "world"
    #|World
    #|
  let multi_line_string_with_interp : String =
    $|Line 1 ""
    $|Line 2 \{1+2}
    $|
  // `#|`内无需转义,
  // `$|`内仅需转义`\{..}`
  assert_eq(multi_line_string, "Hello \"world\"\nWorld\n")
  assert_eq(multi_line_string_with_interp, "Line 1 \"\"\nLine 2 3\n")
}

Map (Mutable, Insertion-Order Preserving)

Map(可变,保留插入顺序)

mbt
///|
test "map literals and common operations" {
  // Map literal syntax
  let map : Map[String, Int] = { "a": 1, "b": 2, "c": 3 }
  let empty : Map[String, Int] = {} // Empty map, preferred
  let also_empty : Map[String, Int] = Map([])
  // From array of pairs
  let from_pairs : Map[String, Int] = Map::from_array([("x", 1), ("y", 2)])

  // Set/update value
  map["new-key"] = 3
  map["a"] = 10 // Updates existing key

  // Get value - returns Option[T]
  guard map is { "new-key": 3, "missing"? : None, .. } else {
    fail("unexpected map contents")
  }

  // Direct access (panics if key missing)
  let value : Int = map["a"] // value = 10

  // Iteration preserves insertion order
  for k, v in map {
    println("\{k}: \{v}") // Prints: a: 10, b: 2, c: 3, new-key: 3
  }

  // Other common operations
  map.remove("b")
  guard map is { "a": 10, "c": 3, "new-key": 3, .. } && map.length() == 3 else {
    // "b" is gone, only 3 elements left
    fail("unexpected map contents after removal")
  }
}
mbt
///|
test "map字面量和常见操作" {
  // Map字面量语法
  let map : Map[String, Int] = { "a": 1, "b": 2, "c": 3 }
  let empty : Map[String, Int] = {} // 空map,推荐写法
  let also_empty : Map[String, Int] = Map([])
  // 从键值对数组创建
  let from_pairs : Map[String, Int] = Map::from_array([("x", 1), ("y", 2)])

  // 设置/更新值
  map["new-key"] = 3
  map["a"] = 10 // 更新现有键

  // 获取值 - 返回Option[T]
  guard map is { "new-key": 3, "missing"? : None, .. } else {
    fail("unexpected map contents")
  }

  // 直接访问(键不存在时触发恐慌)
  let value : Int = map["a"] // value = 10

  // 迭代保留插入顺序
  for k, v in map {
    println("\{k}: \{v}") // 输出: a: 10, b: 2, c: 3, new-key: 3
  }

  // 其他常见操作
  map.remove("b")
  guard map is { "a": 10, "c": 3, "new-key": 3, .. } && map.length() == 3 else {
    // "b"已被移除,仅剩3个元素
    fail("unexpected map contents after removal")
  }
}

View Types

视图类型

Key Concept: View types (
StringView
,
BytesView
,
ArrayView[T]
) are zero-copy, non-owning read-only slices created with the
[:]
syntax. They don't allocate memory and are ideal for passing sub-sequences without copying data, for functions which take
String
,
Bytes
,
Array
, they also take
*View
(implicit conversion).
  • String
    StringView
    via
    s[:]
    or
    s[start:end]
    or
    s[start:]
    or
    s[:end]
  • Bytes
    BytesView
    via
    b[:]
    or
    b[start:end]
    , etc.
  • Array[T]
    ,
    FixedArray[T]
    ,
    ReadOnlyArray[T] → 
    ArrayView[T]
    via
    a[:]
    or
    a[start:end]`, etc.
Important: StringView slice is slightly different due to unicode safety:
s[a:b]
may raise an error at surrogate boundaries (UTF-16 encoding edge case). You have two options:
  • Use
    try! s[a:b]
    if you're certain the boundaries are valid (crashes on invalid boundaries)
  • Let the error propagate to the caller for proper handling
When to use views:
  • Pattern matching with rest patterns (
    [first, .. rest]
    )
  • Passing slices to functions without allocation overhead
  • Avoiding unnecessary copies of large sequences
Convert back with
.to_string()
,
.to_bytes()
, or
.to_array()
when you need ownership. (
moon ide doc StringView
)
核心概念: 视图类型(
StringView
BytesView
ArrayView[T]
)是零拷贝、非拥有的只读切片,通过
[:]
语法创建。它们不分配内存,非常适合传递子序列而无需复制数据,对于接受
String
Bytes
Array
的函数,也接受对应的
*View
(隐式转换)。
  • String
    StringView
    通过
    s[:]
    s[start:end]
    s[start:]
    s[:end]
  • Bytes
    BytesView
    通过
    b[:]
    b[start:end]
  • Array[T]
    FixedArray[T]
    ReadOnlyArray[T] → 
    ArrayView[T]
     通过
    a[:]
    a[start:end]`等
重要提示: StringView切片由于unicode安全性略有不同:
s[a:b]
在代理边界(UTF-16编码边缘情况)可能抛出错误。你有两种选择:
  • 如果你确定边界有效,使用
    try! s[a:b]
    (边界无效时崩溃)
  • 让错误传播给调用者进行适当处理
何时使用视图:
  • 使用剩余模式进行模式匹配(
    [first, .. rest]
  • 将切片传递给函数而无分配开销
  • 避免大序列的不必要复制
当你需要所有权时,使用
.to_string()
.to_bytes()
.to_array()
转换回去。(
moon ide doc StringView

User defined types(
enum
,
struct
)

用户定义类型(
enum
struct

mbt
///|
enum Tree[T] {
  Leaf(T) // Unlike Rust, no comma here
  Node(left~ : Tree[T], T, right~ : Tree[T]) // enum can use labels
} derive(Debug, ToJson) // derive traits for Tree

///|
pub fn Tree::sum(tree : Tree[Int]) -> Int {
  match tree {
    Leaf(x) => x
    // we don't need to write Tree::Leaf, when `tree` has a known type
    Node(left~, x, right~) => left.sum() + x + right.sum() // method invoked in dot notation
  }
}

///|
struct Point {
  x : Int
  y : Int
} derive(Debug, ToJson) // derive traits for Point

///|
pub fn Point::Point(x~ : Int, y~ : Int) -> Point {
  { x, y }
}

///|
test "user defined types: enum and struct" {
  json_inspect(Point(x=10, y=20), content={ "x": 10, "y": 20 })
  debug_inspect(
    Point(x=10, y=20),
    content=(
      #|{ x: 10, y: 20 }
    ),
  )
}
mbt
///|
enum Tree[T] {
  Leaf(T) // 与Rust不同,这里不需要逗号
  Node(left~ : Tree[T], T, right~ : Tree[T]) // 枚举可以使用带标签的参数
} derive(Debug, ToJson) // 为Tree派生trait

///|
pub fn Tree::sum(tree : Tree[Int]) -> Int {
  match tree {
    Leaf(x) => x
    // 当`tree`类型已知时,不需要写Tree::Leaf
    Node(left~, x, right~) => left.sum() + x + right.sum() // 使用点符号调用方法
  }
}

///|
struct Point {
  x : Int
  y : Int
} derive(Debug, ToJson) // 为Point派生trait

///|
pub fn Point::Point(x~ : Int, y~ : Int) -> Point {
  { x, y }
}

///|
test "用户定义类型:enum和struct" {
  json_inspect(Point(x=10, y=20), content={ "x": 10, "y": 20 })
  debug_inspect(
    Point(x=10, y=20),
    content=(
      #|{ x: 10, y: 20 }
    ),
  )
}

Functional
for
loop

函数式
for
循环

mbt
///|
pub fn binary_search(arr : ArrayView[Int], value : Int) -> Result[Int, Int] {
  let len = arr.length()
  // functional for loop:
  // initial state ; [predicate] ; [post-update] {
  // loop body with `continue` to update state
  //} nobreak { // exit block
  // }
  // predicate and post-update are optional
  for i = 0, j = len; i < j; {
    // post-update is omitted, we use `continue` to update state
    let h = i + (j - i) / 2
    if arr[h] < value {
      continue h + 1, j // functional update of loop state
    } else {
      continue i, h // functional update of loop state
    }
  } nobreak { // exit of for loop
    if i < len && arr[i] == value {
      Ok(i)
    } else {
      Err(i)
    }
  } where {
    proof_invariant: 0 <= i && i <= j && j <= len,
    proof_invariant: i == 0 || arr[i - 1] < value,
    proof_invariant: j == len || arr[j] >= value,
    proof_reasoning: (
      #|For a sorted array, the boundary invariants are witnesses:
      #|  - `arr[i-1] < value` implies all arr[0..i) < value (by sortedness)
      #|  - `arr[j] >= value` implies all arr[j..len) >= value (by sortedness)
      #|
      #|Preservation proof:
      #|  - When arr[h] < value: new_i = h+1, and arr[new_i - 1] = arr[h] < value ✓
      #|  - When arr[h] >= value: new_j = h, and arr[new_j] = arr[h] >= value ✓
      #|
      #|Termination: j - i decreases each iteration (h is strictly between i and j)
      #|
      #|Correctness at exit (i == j):
      #|  - By invariants: arr[0..i) < value and arr[i..len) >= value
      #|  - So if value exists, it can only be at index i
      #|  - If arr[i] != value, then value is absent and i is the insertion point
      #|
    ),
  }
}

///|
test "functional for loop control flow" {
  let arr : Array[Int] = [1, 3, 5, 7, 9]
  debug_inspect(binary_search(arr, 5), content="Ok(2)") // Array to ArrayView implicit conversion when passing as arguments
  debug_inspect(binary_search(arr, 6), content="Err(3)")
  // for iteration is supported too
  for i, v in arr {
    println("\{i}: \{v}") // `i` is index, `v` is value
  }
}
You are STRONGLY ENCOURAGED to use functional
for
loops instead of imperative loops WHENEVER POSSIBLE, as they are easier to read and reason about.
mbt
///|
pub fn binary_search(arr : ArrayView[Int], value : Int) -> Result[Int, Int] {
  let len = arr.length()
  // 函数式for循环:
  // 初始状态 ; [条件] ; [更新] {
  // 循环体使用`continue`更新状态
  //} nobreak { // 退出块
  // }
  // 条件和更新是可选的
  for i = 0, j = len; i < j; {
    // 省略更新,使用`continue`更新状态
    let h = i + (j - i) / 2
    if arr[h] < value {
      continue h + 1, j // 函数式更新循环状态
    } else {
      continue i, h // 函数式更新循环状态
    }
  } nobreak { // for循环退出
    if i < len && arr[i] == value {
      Ok(i)
    } else {
      Err(i)
    }
  } where {
    proof_invariant: 0 <= i && i <= j && j <= len,
    proof_invariant: i == 0 || arr[i - 1] < value,
    proof_invariant: j == len || arr[j] >= value,
    proof_reasoning: (
      #|对于排序数组,边界不变量是证据:
      #|  - `arr[i-1] < value`意味着所有arr[0..i) < value(由有序性)
      #|  - `arr[j] >= value`意味着所有arr[j..len) >= value(由有序性)
      #|
      #|保留证明:
      #|  - 当arr[h] < value: new_i = h+1,且arr[new_i - 1] = arr[h] < value ✓
      #|  - 当arr[h] >= value: new_j = h,且arr[new_j] = arr[h] >= value ✓
      #|
      #|终止性: j - i每次迭代都减小(h严格在i和j之间)
      #|
      #|退出时的正确性(i == j):
      #|  - 根据不变量: arr[0..i) < value且arr[i..len) >= value
      #|  - 所以如果值存在,只能在索引i处
      #|  - 如果arr[i] != value,则值不存在,i是插入点
      #|
    ),
  }
}

///|
test "函数式for循环控制流" {
  let arr : Array[Int] = [1, 3, 5, 7, 9]
  debug_inspect(binary_search(arr, 5), content="Ok(2)") // 传递参数时Array隐式转换为ArrayView
  debug_inspect(binary_search(arr, 6), content="Err(3)")
  // 也支持for迭代
  for i, v in arr {
    println("\{i}: \{v}") // `i`是索引,`v`是值
  }
}
强烈建议尽可能使用函数式
for
循环而非命令式循环,因为它们更易读和推理。

Loop Invariants with
where
Clause

where
子句的循环不变量

The
where
clause attaches machine-checkable invariants and human-readable reasoning to functional
for
loops. This enables formal verification thinking while keeping the code executable. Note for trivial loops, you are encouraged to convert it into
for .. in
so no reasoning is needed.
Syntax:
mbt
for ... {
  ...
} where {
  invariant : <boolean_expr>,   // checked at runtime in debug builds
  invariant : <boolean_expr>,   // multiple invariants allowed
  reasoning : <string>         // documentation for proof sketch
}
Writing Good Invariants:
  1. Make invariants checkable: Invariants must be valid MoonBit boolean expressions using loop variables and captured values.
  2. Use boundary witnesses: For properties over ranges (e.g., "all elements in arr[0..i) satisfy P"), check only boundary elements. For sorted arrays,
    arr[i-1] < value
    implies all
    arr[0..i) < value
    .
  3. Handle edge cases with
    ||
    : Use patterns like
    i == 0 || arr[i-1] < value
    to handle boundary conditions where the check would be out of bounds.
  4. Cover three aspects in reasoning:
    • Preservation: Why each
      continue
      maintains the invariants
    • Termination: Why the loop eventually exits (e.g., a decreasing measure)
    • Correctness: Why the invariants at exit imply the desired postcondition
where
子句为函数式
for
循环附加机器可检查的不变量人类可读的推理。这使得在保持代码可执行的同时,能够进行形式化验证思考。对于简单循环,建议转换为
for .. in
,无需推理。
语法:
mbt
for ... {
  ...
} where {
  invariant : <boolean_expr>,   // 在调试构建中运行时检查
  invariant : <boolean_expr>,   // 允许多个不变量
  reasoning : <string>         // 证明草图的文档
}
编写良好的不变量:
  1. 使不变量可检查: 不变量必须是使用循环变量和捕获值的有效MoonBit布尔表达式。
  2. 使用边界证据: 对于范围属性(例如"arr[0..i)中的所有元素满足P"),仅检查边界元素。对于排序数组,
    arr[i-1] < value
    意味着所有
    arr[0..i) < value
  3. 使用
    ||
    处理边缘情况
    : 使用
    i == 0 || arr[i-1] < value
    等模式处理检查会越界的边界条件。
  4. 在推理中涵盖三个方面:
    • 保留性: 为什么每个
      continue
      都能保持不变量
    • 终止性: 为什么循环最终会退出(例如递减的度量)
    • 正确性: 为什么退出时的不变量意味着期望的后置条件

Label and Optional Parameters

标签和可选参数

Good example: use labeled and optional parameters
mbt
///|
fn g(
  positional : Int,
  required~ : Int,
  optional? : Int, // no default => Option
  optional_with_default? : Int = 42, // default => plain Int
) -> String {
  // These are the inferred types inside the function body.
  let _ : Int = positional
  let _ : Int = required
  let _ : Int? = optional
  let _ : Int = optional_with_default
  // `to_repr` (from the prelude `Debug` trait) renders Option via the
  // non-deprecated `Show for Repr`, avoiding the deprecated `Show for Option`.
  "\{positional},\{required},\{to_repr(optional)},\{optional_with_default}"
}

///|
test {
  inspect(g(1, required=2), content="1,2,None,42")
  inspect(g(1, required=2, optional=3), content="1,2,Some(3),42")
  inspect(g(1, required=4, optional_with_default=100), content="1,4,None,100")
}
Misuse:
arg : Type?
is not an optional parameter. Callers still must pass it (as
None
/
Some(...)
).
mbt
///|
fn with_config(a : Int?, b : Int?, c : Int) -> String {
  "\{to_repr(a)},\{to_repr(b)},\{c}"
}

///|
test {
  inspect(with_config(None, None, 1), content="None,None,1")
  inspect(with_config(Some(5), Some(5), 1), content="Some(5),Some(5),1")
}
Anti-pattern:
arg? : Type?
(no default => double Option). If you want a defaulted optional parameter, write
b? : Int = 1
, not
b? : Int? = Some(1)
.
mbt
///|
fn f_misuse(a? : Int?, b? : Int = 1) -> Unit {
  let _ : Int?? = a // rarely intended
  let _ : Int = b
}
// How to fix: declare `(a? : Int, b? : Int = 1)` directly.

///|
fn f_correct(a? : Int, b? : Int = 1) -> Unit {
  let _ : Int? = a
  let _ : Int = b
}

///|
test {
  f_misuse(b=3)
  f_misuse(a=Some(5), b=2) // works but confusing
  f_correct(b=2)
  f_correct(a=5)
}
Bad example:
arg : APIOptions
(use labeled optional parameters instead)
mbt
///|
/// Do not use struct to group options.
struct APIOptions {
  width : Int?
  height : Int?
}

///|
fn not_idiomatic(opts : APIOptions, arg : Int) -> Unit {

}

///|
test {
  // Hard to use in call site
  not_idiomatic({ width: Some(5), height: None }, 10)
  not_idiomatic({ width: None, height: None }, 10)
}
良好示例:使用标签和可选参数
mbt
///|
fn g(
  positional : Int,
  required~ : Int,
  optional? : Int, // 无默认值 => Option
  optional_with_default? : Int = 42, // 有默认值 => 普通Int
) -> String {
  // 函数体内的推断类型。
  let _ : Int = positional
  let _ : Int = required
  let _ : Int? = optional
  let _ : Int = optional_with_default
  // `to_repr`(来自前置`Debug` trait)通过非弃用的`Show for Repr`渲染Option,避免弃用的`Show for Option`。
  "\{positional},\{required},\{to_repr(optional)},\{optional_with_default}"
}

///|
test {
  inspect(g(1, required=2), content="1,2,None,42")
  inspect(g(1, required=2, optional=3), content="1,2,Some(3),42")
  inspect(g(1, required=4, optional_with_default=100), content="1,4,None,100")
}
错误用法:
arg : Type?
不是可选参数。 调用者仍必须传递它(作为
None
/
Some(...)
)。
mbt
///|
fn with_config(a : Int?, b : Int?, c : Int) -> String {
  "\{to_repr(a)},\{to_repr(b)},\{c}"
}

///|
test {
  inspect(with_config(None, None, 1), content="None,None,1")
  inspect(with_config(Some(5), Some(5), 1), content="Some(5),Some(5),1")
}
反模式:
arg? : Type?
(无默认值 => 双重Option)。 如果你想要带默认值的可选参数,编写
b? : Int = 1
,而非
b? : Int? = Some(1)
mbt
///|
fn f_misuse(a? : Int?, b? : Int = 1) -> Unit {
  let _ : Int?? = a // 很少是预期的
  let _ : Int = b
}
// 修复方法:直接声明`(a? : Int, b? : Int = 1)`。

///|
fn f_correct(a? : Int, b? : Int = 1) -> Unit {
  let _ : Int? = a
  let _ : Int = b
}

///|
test {
  f_misuse(b=3)
  f_misuse(a=Some(5), b=2) // 可行但易混淆
  f_correct(b=2)
  f_correct(a=5)
}
不良示例:
arg : APIOptions
(改用标签可选参数)
mbt
///|
/// 不要使用结构体来分组选项。
struct APIOptions {
  width : Int?
  height : Int?
}

///|
fn not_idiomatic(opts : APIOptions, arg : Int) -> Unit {

}

///|
test {
  // 调用时难以使用
  not_idiomatic({ width: Some(5), height: None }, 10)
  not_idiomatic({ width: None, height: None }, 10)
}

More details

更多细节

For deeper syntax, types, and examples, read
references/moonbit-language-fundamentals.mbt.md
.
如需更深入的语法、类型和示例,请阅读
references/moonbit-language-fundamentals.mbt.md