moonbit-practice
MoonBit Practice Guide
Best practices for AI when generating MoonBit code.
If you don't understand something here, use moonbit-docs skill to search the official documentation.
Guidelines
Code Navigation: Prefer moon ide over Read/Grep
In MoonBit projects, prefer moon ide commands over Read tool or grep.
# ❌ Avoid: Reading files directly
Read src/parser.mbt
# ✅ Recommended: Find definitions from symbols
moon ide peek-def Parser::parse
moon ide goto-definition -tags 'pub fn' -query 'parse'
# ❌ Avoid: Searching with grep
grep -r "fn parse" .
# ✅ Recommended: Semantic search
moon ide find-references parse
moon ide outline src/parser.mbt
Why:
moon ideprovides semantic search (distinguishes definitions from call sites)- grep picks up comments and strings
moon docquickly reveals APIs
Other Rules
- Use
moon doc '<Type>'to explore APIs before implementing - Check reference/configuration.md before editing moon.pkg.json / moon.mod.json
- Check reference/language.md for detailed language feature examples (types, traits, pattern matching, etc.)
- Check reference/mbtx.md for single-file
.mbtxscripts (moon run script.mbtx, stdin viamoon run -, inlineimport { }block; nightly only)
Common Pitfalls
- Don't use uppercase for variables/functions - compilation error
mutis only for reassignment, not field mutation - Array push doesn't need itreturnis unnecessary - last expression is the return value- Methods require
Type::prefix ++--not supported - usei = i + 1ori += 1- No
tryneeded for error propagation - automatic (unlike Swift) - No
awaitkeyword - just declare withasync fn - Prefer range for over C-style -
for i in 0..<n {...} nobreaknotelsefor functional for-loop exit values (elseis deprecated)- Legacy syntax:
function_name!(...)andfunction_name(...)?are deprecated for { ... }is deprecated - usefor ;; { ... }orwhile true { ... }for infinite loops- Cross-package
.syntax forimplremoved -.method call only works within the same package
Common Syntax Mistakes by AI
Type Parameter Position
///| NG: fn identity[T] is old syntax
fn identity[T](val: T) -> T { val }
///| OK: Type parameter comes right after fn
fn[T] identity(val: T) -> T { val }
raise Syntax
///|
/// NG: -> T!Error was removed
fn parse(s: String) -> Int!Error { ... }
///|
/// OK: Use raise keyword
fn parse(s: String) -> Int raise Error { ... }
Int raise is shorthand for Int raise Error.
async fn implicitly raises by default; use noraise to enforce no errors.
Macro Calls
///|
/// NG: ! suffix was removed
assert_true!(true)
///|
/// OK
assert_true(true)
Multi-line Text
let text =
#|line 1
#|line 2
Comments and Block Separators
///| is a block separator. /// comments attach to the following ///| block.
///|
/// This function is foo
fn foo() -> Unit { ... }
///|
/// This function is bar
fn bar() -> Unit { ... }
Avoid consecutive ///| at the file beginning as they create separate blocks.
Snapshot Tests
moon test -u auto-updates content="" in inspect(val).
test "snapshot" {
inspect([1, 2, 3], content="") // auto-filled by moon test -u
}
After running:
test "snapshot" {
inspect([1, 2, 3], content="[1, 2, 3]")
}
Doc Tests
Available in .mbt.md files or ///| inline comments.
| Code Block | Behavior |
|---|---|
```mbt check |
Checked by LSP |
```mbt test |
Executed as test {...} |
```moonbit |
Display only (not executed) |
Example (inline comment):
///|
/// Increment an integer by 1
/// ```mbt test
/// inspect(incr(41), content="42")
/// ```
pub fn incr(x : Int) -> Int {
x + 1
}
Pre-release Checklist
Run before releasing:
moon fmt # Format code
moon info # Generate type definition files
pkg.generated.mbti is auto-generated by moon info. Don't edit it directly.
Exploring Built-in Type Methods
moon doc StringView # StringView methods
moon doc Array # Array methods
moon doc Map # Map methods
Quick Reference
| Topic | Command | Details |
|---|---|---|
| Test | moon test |
https://docs.moonbitlang.com/en/stable/language/tests |
| Update snapshots | moon test -u |
Same as above |
| Filtered test | moon test --filter 'glob' |
Run specific tests |
| Benchmark | moon bench |
https://docs.moonbitlang.com/en/stable/language/benchmarks |
| Doc Test | moon check / moon test |
https://docs.moonbitlang.com/en/stable/language/docs |
| Format | moon fmt |
- |
| Generate types | moon info |
- |
| Doc reference | moon doc <Type> |
- |
| Workspace init | moon work init |
See reference/configuration.md |
| Workspace add | moon work use mod1 mod2 |
Add modules to workspace |
| API usage analysis | moon ide analyze . |
Show public API usage stats |
moon ide Tools
More accurate than grep for code navigation. See reference/ide.md for details.
# Show symbol definition
moon ide peek-def Parser::read_u32_leb128
# Package outline
moon ide outline .
# Find references
moon ide find-references TranslationUnit
# Jump to type definition (with location)
moon ide peek-def Parser -loc src/parse.mbt:46:4
# Show type signature and docs at location (unlike peek-def, shows inferred type + doc comments)
moon ide hover my_func --loc src/lib.mbt:10:4
# Rename symbol across the project
moon ide rename old_name new_name
# Analyze public API usage (v0.8.3+)
moon ide analyze . # Current package
moon ide analyze internal/* # Glob pattern
Functional for loop
Prefer functional for loops whenever possible. More readable and easier to reason about.
// Functional for loop with state
for i = 0, sum = 0; i <= 10 {
continue i + 1, sum + i // Update state
} nobreak {
sum // Value at loop exit (nobreak, not else)
}
// Range for (recommended)
for i in 0..<n { ... }
for i, v in array { ... } // index and value
// Range for with extra loop variable (v0.8.3+)
for x in xs; sum = 0 {
continue sum + x
} nobreak {
sum
}
// Infinite loop (for { } is deprecated)
for ;; { ... }
String Constants
const supports string concatenation and interpolation (v0.8.3+):
const Hello : String = "Hello"
const HelloWorld : String = Hello + " world"
const Message : String =
$|========
$|\{HelloWorld}
$|========
Error Handling
MoonBit uses checked errors. See reference/ffi.md for details.
///| Declare error type
suberror ParseError {
InvalidEof
InvalidChar(Char)
}
///| Declare with raise, auto-propagates
fn parse(s: String) -> Int raise ParseError {
if s.is_empty() { raise ParseError::InvalidEof }
...
}
///| Convert to Result
let result : Result[Int, ParseError] = try? parse(s)
///| Handle with try-catch
parse(s) catch {
ParseError::InvalidEof => -1
_ => 0
}
CI and Publishing
Third-party actions
Prefer the official curl installer (assets/ci.yaml) or Nix with moonbit-overlay (assets/ci-nix.yaml) over third-party actions such as hustcer/setup-moonbit@v1. The installer is short and adds nothing to the action supply chain; the Nix path is fully reproducible when a flake.nix is present.
moon update is mandatory
Runners start with an empty registry index. Any registry-hosted MoonBit dependency makes moon check / moon test / moon build fail with Failed to resolve registry dependency until you run moon update. Put it immediately after installing the CLI in every workflow that touches mooncakes.
Publishing an npm package whose build depends on MoonBit
Some projects (e.g. Vite plugins, language tooling) ship as npm packages but run moon inside pnpm build — for instance pnpm build:parser that calls moon -C tools/parser build --release --target js. In that case the npm publish workflow needs both the MoonBit CLI and moon update before pnpm build, otherwise the build fails on CI.
See assets/publish-to-npm.yaml for a minimal release-triggered publish workflow that uses OIDC Trusted Publishing (no NPM_TOKEN) and sets up MoonBit correctly. Pair it with a release automation tool (see the npm-release skill for a release-please + OIDC setup) to avoid manual npm publish calls.
Assets
assets/ci.yaml— GitHub Actions CI (curl installer)assets/ci-nix.yaml— GitHub Actions CI with Nix (moonbit-overlay)assets/publish-to-npm.yaml— release-triggered npm publish with MoonBit build and OIDC Trusted Publishing
Nix Setup (moonbit-overlay)
moonbit-community/moonbit-overlay provides a Nix flake overlay for reproducible MoonBit builds.
Minimal flake.nix for a MoonBit project:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
moonbit-overlay.url = "github:moonbit-community/moonbit-overlay";
moon-registry = {
url = "git+https://mooncakes.io/git/index";
flake = false;
};
};
outputs = { nixpkgs, moonbit-overlay, moon-registry, ... }:
let
system = "x86_64-linux"; # or aarch64-darwin, etc.
pkgs = import nixpkgs {
inherit system;
overlays = [ moonbit-overlay.overlays.default ];
};
moonHome = pkgs.moonPlatform.bundleWithRegistry {
cachedRegistry = pkgs.moonPlatform.buildCachedRegistry {
moonModJson = ./moon.mod.json;
registryIndexSrc = moon-registry;
};
};
in {
devShells.${system}.default = pkgs.mkShellNoCC {
packages = [ moonHome pkgs.git ];
env.MOON_HOME = "${moonHome}";
};
};
}
Key APIs from the overlay:
pkgs.moonPlatform.buildMoonPackage- Build a MoonBit package as a Nix derivationpkgs.moonPlatform.bundleWithRegistry- Create a MOON_HOME with cached registrypkgs.moonPlatform.buildCachedRegistry- Pre-fetch mooncakes registry dependencies
More from mizchi/skills
empirical-prompt-tuning
Methodology for iteratively improving agent-facing instructions (skills / slash commands / CLAUDE.md / code-gen prompts) by having a bias-free executor run them and evaluating two-sidedly (executor self-report + instruction-side metrics) until improvements plateau. Use after creating or revising a prompt or skill.
38gh-fix-ci
Debug or fix failing GitHub PR checks running in GitHub Actions. Inspects checks/logs via `gh`, drafts a fix plan, and implements only after explicit approval. Out of scope: external CI (e.g. Buildkite) — report only the details URL.
9tech-article-reproducibility
Evaluate the reproducibility of technical articles. Dispatch a subagent to simulate a first-time reader reproducing the work locally and list missing information. Use as the final check on a draft before publication.
8playwright-test
Best practices and reference for Playwright Test (E2E). Covers how to write tests, avoiding fixed waits, network triggers, DnD, shard/retry setup on GitHub Actions, and more. Use when writing, reviewing, or configuring CI for Playwright tests.
6ast-grep-practice
Operate ast-grep as a project lint tool. Covers sgconfig.yml, fix/rewrite rules, constraints, transform, testing, and CI. Use when writing rules ast-grep can express but general-purpose linters cannot.
6apm-usage
Use APM (Agent Package Manager) to manage agent skills and dependencies. Use when adding, removing, or updating skills in a project or globally, creating skills for a repository, or configuring apm.yml.
6