moonbit-practice について
AIによる MoonBit コード生成のための実践ガイドです。構文、プロジェクト設定、テスト、コード検索を扱い、MoonBit v0.10.12で確認された注意点をまとめています。
主な機能
- 01MoonBit の構文と型の指針
- 02moon ide によるコード検索
- 03プロジェクトとワークスペース設定
- 04テストとスナップショットの実践
- 05構文上のよくある落とし穴
活用シーン
- 01現行構文に沿った MoonBit コード生成
- 02MoonBit プロジェクトでの API 定義検索
- 03モジュール、パッケージ、ワークスペースの設定
- 04テスト作成とスナップショット更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mizchi/ skills --skill moonbit-practiceインストール後、Claude Code で/moonbit-practiceを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdassets/ci-nix.yamlci.yamlmoon.modmoon.pkgpublish-to-npm.yamlreference/configuration.mdffi-native.mdffi.mdide.mdlanguage.mdmbtx.mdnix.mdperformance.mdrefactor.mdskills-marketplace.mdstdlib.mdtesting.md詳細
MoonBit 実践ガイド
MoonBit コードを生成する際の AI 向けベストプラクティスです。
内容が分からない場合は、moonbit-docs スキルを使って公式ドキュメントを検索してください。
MoonBit v0.10.12 で検証済み(ドキュメントのスナップショットは 2026-09)。MoonBit はまだ 1.0 より前の段階にあり、マイナーリリースでも構文が変更されることがあります。ここに記載された構文が拒否された場合は、 まず
moon fmtを実行してください(非推奨の構文のほとんどは自動移行されます)。その後、moon explain <error-code>を確認してください。
ガイドライン
コードのナビゲーション:Read/Grep より moon ide を優先する
MoonBit プロジェクトでは、Read ツールや grep よりも moon ide コマンドを優先してください。
# ❌ Avoid: Reading files directly
Read src/parser.mbt
# ✅ Recommended: Find definitions from symbols
moon ide peek-def Parser::parse
moon ide doc 'Parser::*parse*' # glob search over symbols
# ❌ Avoid: Searching with grep
grep -r "fn parse" .
# ✅ Recommended: Semantic search
moon ide find-references parse
moon ide outline src/parser.mbt
理由:
moon ideはセマンティック検索を提供します(定義と呼び出し箇所を区別します)- grep ではコメントや文字列もヒットします
moon ide docを使うと API をすばやく確認できます
moon がインストールされていない場合(サンドボックス、レビュー専用のチェックアウト)、
このガイドが前提としている検証手順(moon fmt、moon check、moon test -u、
moon ide doc)は利用できません。その場合:
- 後で
moon test -uが値を補完できるよう、すべてのスナップショットのcontent=""はそのままにします。 reference/stdlib.mdはパッケージの概要であり、シグネチャを網羅した索引ではありません。必要な シンボルが記載されておらず、moon ide docも実行できない場合は、うろ覚えの構文ではなく、このガイドに 記載された構文を優先し、未検証であることを明記してください。- 検証できていない部分を、要約で明確に伝えてください。
ツールが生成するはずの値を手書きしないでください。
その他のルール
- 実装前に
moon ide doc '<Type>'を使って API を調べてください。moon doc <SYMBOL>は非推奨です。警告を表示し、moon ide docを案内します - 設定形式には DSL の
moon.mod/moon.pkgを使用してください。 JSON 形式のmoon.mod.json/moon.pkg.jsonは非推奨で、削除が予定されています。moon fmtで JSON から DSL に移行できます。サンプル:assets/moon.mod、assets/moon.pkg。いずれかを編集する前に reference/configuration.md を確認してください - 実行可能パッケージには
pkgtype(kind: "executable")を使います。options("is-main": true)は使いません。pkgtype(kind: "foreign_library")はoptions(link: true)に代わるものです。通常のライブラリでは宣言は不要です - 1 つのリポジトリに複数のモジュールがある場合は、単独のモジュールではなくワークスペース(
moon.work)を使ってください。moon work init <dirs>でマニフェストを作成できます。メンバーはビルドコンテキストを共有し、互いをローカルで解決します。moon.mod内のパス依存関係は非推奨で、moon.workの使用が推奨されています。reference/configuration.md の「Workspace」を参照してください - 公開用フィルターは
.moonignoreに記述します(存在しない場合は.gitignoreにフォールバックします)。moon.modの非推奨フィールドinclude/excludeは使いません - 以下の例では
pubを省略しています。 下流パッケージまたは JS/Wasm ホストから見える必要があるものにはpubが必要です(pub fn、pub struct、pub suberror、pub impl、pub extend)。パッケージ内でのみ使う項目には不要です - テストファイル:
*_test.mbtはブラックボックステスト(パッケージの公開 API のみを参照できます)、*_wbtest.mbtはホワイトボックステスト(プライベート項目も参照できます)です。単独のtest "..." { }ブロックは、これらのいずれかに記述するか、ホワイトボックステストの場合はソースファイル内に記述します。ブラックボックステストでは、追加のインポートなしでパッケージエイリアス(@mypackage.f())を使って同じパッケージにアクセスできます。import { ... } for "test"のエントリーが必要なのは、使用する他のパッケージのみです - 実行可能ファイルのエントリーポイント:
fn main { ... }— パラメーターリストも括弧もありません。非同期プログラムではasync fn main { ... }を使います。引数は@env.args()で読み取ります。パッケージにはpkgtype(kind: "executable")が必要です - 可視性:
pubは名前を公開します。pub(all)はさらに、構造体のフィールド、または列挙型やsuberrorのコンストラクターも公開します。これらは別のパッケージでパターンマッチングするために必要です。通常のpub struct/pub suberrorは外部から読み取り専用です。構築やパターンマッチングができるのは、そのパッケージ内のみです - このスキルのリファレンスファイル:
reference/language.md(型、トレイト、パターンマッチング)、reference/testing.md(テストの構成、スナップショット、プロパティテスト、ベンチマーク)、reference/stdlib.md(コア API)、reference/configuration.md(moon.mod/moon.pkg/ ワークスペース)、reference/ide.md(moon ide)、reference/ffi.mdとreference/ffi-native.md(FFI)、reference/performance.md、reference/refactor.md、reference/nix.md、reference/mbtx.md、reference/skills-marketplace.md。競合がある場合は、SKILL.md を優先してください。 - 単一ファイルの
.mbtxスクリプト(moon run script.mbtx、moonx script.mbtx、moon run -による stdin、moon run -c '...'によるインラインソース、import { }のプリアンブルブロック)については、reference/mbtx.md を確認してください - skills.mooncakes.io 向けの実行可能ファイルを定義または公開する前に、reference/skills-marketplace.md を確認してください。パッケージ内の
SKILL.md、Wasm ターゲット、moonx、ホスト機能、ポリシー、リリース検証について説明しています
よくある落とし穴
- 変数/関数名に大文字を使わないでください — コンパイルエラーになります
mutは再代入にのみ使い、フィールドの変更には使いません — 配列への追加にmutは必要ありませんreturnは不要です — 最後の式が戻り値になります- メソッドには
Type::プレフィックスが必要です ++と--はサポートされていません —i = i + 1またはi += 1を使ってください- エラーの伝播に
tryは不要です — 自動的に行われます(Swiftとは異なります) awaitキーワードはありません —async fnで宣言するだけです- Cスタイルより範囲ベースのforを優先してください —
for i in 0..<n {...} - 関数型forループの終了値には
elseではなくnobreakを使ってください(elseは非推奨です) - レガシー構文:
function_name!(...)とfunction_name(...)?は非推奨です for { ... }は非推奨です — 無限ループにはfor ;; { ... }またはwhile true { ... }を使ってください- 別パッケージのトレイトメソッドで
.を使うにはpub extendが必要です —implだけでは、別パッケージからvalue.f()を使えるようにはなりません。明示的にアタッチしてください(pub extend Type with Trait::{ f })。pubを付けない場合、アタッチはパッケージ内に限定されます。また、extendがなければ呼び出し側はTrait::f(value)と書く必要があります trait/implのメソッドにはfnキーワードが必要です(v0.10.0) —moon fmtが古いコードを移行します。下記の「トレイトまたはimplの移行」を参照してください- トレイトメソッドは
extendでアタッチしてください(v0.10.4) — 暗黙のimplからメソッドへのアタッチは非推奨です。下記の「トレイトまたはimplの移行」を参照してください try?は廃止されました(v0.10.0) —ResultにはOk(expr) catch { e => Err(e) }を使い、エラー時にパニックさせるにはtry!を使ってください- 構造体の
fn new(..)コンストラクターは削除されました(v0.10.0) — ユーザー定義コンストラクターfn Type::Type(..)を使ってください。v0.10.4以降は構造体に限らず、あらゆる型で定義できます immut/arrayは削除されました(v0.10.9) — v0.9で非推奨になりました。moon.pkgに"moonbitlang/core/immut/vector"をインポートし(デフォルトのエイリアスは@immut/vector、つまりmoonbitlang/core/の後のパスです)、@immut/vector.Vectorを使ってください- 不変コンテナーの
from_arrayは非推奨です(v0.10.4) — 型名を使ったコンストラクターを使ってください。例:@immut/vector.Vector([1, 2, 3]) - View演算子では負のインデックスが使えなくなりました(v0.10.4) —
xs[-1:]は「末尾から」を意味しなくなりました moonbitlang/sysは非推奨です(v0.10.9) —moonbitlang/core/envを使ってください:let argv = @env.args()(要素0はプログラムを表すため、CLI引数はargv[1:]です)@debug.to_repr(x)は非推奨です(v0.10.9) —@debug.Repr(x)を使ってくださいelseのないguardは、必ず成功すると証明できる必要があります(v0.10.9) — そうでない場合はE0087になります。パニックさせる場合はguard!と書いてください- orパターンの
with分岐には括弧が必要です(v0.10.9) —Some(x) | (None with x = 0) - コンテナー型/カスタム型では
inspectの代わりにdebug_inspectを使ってください(v0.9.2) — コンテナー型(Array/Map/Set/Option/Result/タプル)のShowは非推奨です。「スナップショットテスト」を参照してください - デフォルトのビルドターゲットは
wasmです(v0.10.9) —--target js/--target nativeを明示的に指定するか、moon.modでpreferred_targetを設定してください
AIがよく間違える構文
型パラメーターの位置
///| 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構文
///|
/// NG: -> T!Error was removed
fn parse(s: String) -> Int!Error { ... }
///|
/// OK: Use raise keyword
fn parse(s: String) -> Int raise Error { ... }
Int raiseはInt raise Errorの省略形です。
async fnはデフォルトで暗黙的にエラーを発生させます。エラーを発生させないようにするにはnoraiseを使ってください。
マクロ呼び出し
///|
/// NG: ! suffix was removed
assert_true!(true)
///|
/// OK
assert_true(true)
トレイトまたはimplの移行
同じコードに対して常に2つの変更が同時に適用されるため、1回の作業で両方を行ってください。
- v0.10.0 — トレイトメソッドと
implの両方にfnを追加します。 - v0.10.4 — 呼び出し側がドット構文(
value.f())で呼び出すトレイトメソッドごとに、extend宣言を追加します。これがないと、ドット呼び出しは非推奨の暗黙的なアタッチに依存します。
moon fmtが実行するのはステップ1のみです。ステップ2は手動で行います。
///| NG: method without fn (deprecated, warns then removed)
trait I {
f(Self) -> Unit
}
impl I for Int with f(_) {}
///| OK: fn keyword on both the trait method and the impl
trait I {
fn f(Self) -> Unit
}
impl I for Int with fn f(_) {}
多相トレイトメソッドでは、メソッド固有の型パラメーターは**fnの後**に置き、impl固有の型パラメーターはimplの後に置きます。
trait Logger {
fn[X : Show] write_object(Self, X) -> Unit
}
impl Logger for StringBuilder with fn write_object(self, x) {
self.write_string(x.to_string())
}
impl[A] Poly for Array[A] with fn[X] f(self, x : X) { ... }
// ^^^ impl type params ^^^ method type params
moon fmtは古いトレイト/impl構文を自動的に書き換えます。
extend でトレイトメソッドを付与する(v0.10.4)
以前は、impl Trait for Type を記述すると、すべてのトレイトメソッドが暗黙的に Type のドット呼び出し可能なメソッドとして付与されていました。これは非推奨です。上流のトレイトにデフォルトメソッドが追加されると、既存の value.f() が曖昧になる可能性があります。代わりに、付与するメソッドを明示的に宣言してください。
///| NG (deprecated): relying on implicit attachment
impl Show for Point with fn output(self, logger) { ... }
let s = p.output(logger) // implicit attachment, warns under warning 79
///| OK: attach the methods you want callable with dot syntax
impl Show for Point with fn output(self, logger) { ... }
pub extend Point with Show::{ output, to_string }
- 基本形:
extend Type with Trait::{ method1, method2 }。pubを付けると、付与されたメソッドが公開されます。付けない場合はパッケージ内に限定されます。 - 付与されたメソッドがトレイトのデフォルト実装を使う場合、
SelfはTypeに特殊化されます。 - トレイトオブジェクトにも付与できます:
extend &Derived with Super::{ method }。 extendがない場合も、トレイト経由で呼び出せます:Trait::method(value, ...)。- 警告
implicit_impl_as_method(79)は非推奨のパターンを検出します。デフォルトでは無効なので、ライブラリを移行する際は有効にしてください(moon check --warn-list +79)。
構造体、コンストラクター、メソッド
削除された特殊形式 fn new(..) は、通常のユーザー定義コンストラクター fn Type::Type(..) に置き換えられます。これは v0.10.4 以降、構造体に限らずあらゆる型で利用できます。メソッドではレシーバーを self : Self として受け取ります。
///| Struct with derives
struct Point {
x : Int
y : Int
} derive(Eq, Debug)
///| User-defined constructor (replaces removed `fn new`)
fn Point::Point(x : Int, y : Int) -> Point {
{ x, y } // field-punning struct literal
}
///| Method: receiver `self : Self`, last expression is the return value
fn Point::manhattan(self : Self) -> Int {
self.x.abs() + self.y.abs()
}
///| Call site: a declared constructor is called by the *type name*, not `Type::Type`
let p = Point(1, 2)
///| Constructors are ordinary functions, so they take labelled and optional
/// arguments and may raise:
fn Endpoint::Endpoint(host : String, port? : Int = 80) -> Endpoint {
Host(host, port)
}
let e = Endpoint("example.com", port=443)
コンストラクターを宣言しなくても、構造体はレコードリテラル Point::{ x: 1, y: 2 }(型がわかっている場合は { x: 1, y: 2 } でも可)で構築できます。構築時に検証、デフォルト値、または短い呼び出し形式が必要な場合は fn Type::Type(..) を宣言し、その後はすべての呼び出し箇所で Point(1, 2) を使うことをおすすめします。
複数行テキスト
let text =
#|line 1
#|line 2
コメントとブロック区切り
///| はブロック区切りです。/// コメントは、後続する ///| ブロックに付与されます。
///|
/// This function is foo
fn foo() -> Unit { ... }
///|
/// This function is bar
fn bar() -> Unit { ... }
ファイル先頭で ///| を連続させないでください。別々のブロックが作成されます。
スナップショットテスト
moon test -u は inspect(val)/debug_inspect(val) の空の content="" を自動で埋めます。使い分け(v0.9.2 以降):
- プリミティブ型/文字列 →
inspect(ここではShowで問題ありません) - コンテナー型(
Array/Map/Set/Option/Result/タプル)およびカスタム型 →debug_inspect。これらのコンテナーでのShowは非推奨であり、Debugなら構造化されたインデント付きの出力が得られるためです。Showは人間向けのフォーマットにのみ使用してください。 - 深くネストされた構造 →
@json.inspect。フォーマット後も JSON 出力の可読性が保たれます。derive(ToJson)が必要です。
実行時の値ではなく、検査する式の静的型に基づいて選択してください。Result[Int, _] は Ok(123) を保持している場合でもコンテナーです。また、このルールは再帰的に適用されます。カスタム型のスナップショット作成やアサートを行うには(コンテナー内にネストされている場合も含め)、そのカスタム型に derive(Debug) が必要です。また、@debug.assert_eq による等価性チェックには derive(Eq) も必要です。
test "snapshot" {
inspect("hello", content="") // primitive/string -> inspect
debug_inspect([1, 2, 3], content="") // container -> debug_inspect
}
///| Custom type: derive Debug to snapshot, derive Eq to assert_eq
struct P {
x : Int
} derive(Eq, Debug)
test "custom type" {
debug_inspect(P::{ x: 1 }, content="")
@debug.assert_eq(P::{ x: 1 }, P::{ x: 1 })
}
moon test -u の実行後:
test "snapshot" {
inspect("hello", content="hello")
debug_inspect([1, 2, 3], content="[1, 2, 3]")
}
content="" と記述し、moon test -u に埋めてもらってください。期待される文字列を手書きしないでください。Debug のレンダリングは構造を反映し、Show とは異なるため、推測した値では初回実行時にテストが失敗します。ツールチェーンを実行できない場合は、すべての content="" を空のままにして、その旨を伝えてください。空のスナップショットは正直な TODO ですが、誤った値は誤ったアサーションになります。
以下は Debug が生成する形式です。既存のスナップショットを読むために示しています。新しいものを書くためではありません。書き込みは引き続き -u の役目です。
| 検査対象の値 | debug_inspect の出力 |
|---|---|
[1, 2, 3] | [1, 2, 3] |
Point::{ x: 1, y: 2 }(構造体) | { x: 1, y: 2 } |
Shape::Rect(width=3, height=4)(列挙型、ラベル付き) | Rect(width=3, height=4) |
Host("example.com", 443)(列挙型、位置指定) | Host("example.com", 443) |
Err(InvalidEof)(Result と suberror) | Err(InvalidEof) |
長い値は折り返されてインデントされます。これも -u に書き込ませるべき理由のひとつです。
プロパティテスト(v0.10.9)
QuickCheck は標準ライブラリの moonbitlang/core/quickcheck(別名 @quickcheck)に含まれるようになりました。moonbitlang/quickcheck を別途依存関係に追加しないでください。エントリーポイントは 2 つあります。
@quickcheck.check(property)— 失敗時は例外を発生させ、成功時は何も出力しません。テスト対象の型にはArbitrary + Shrink + Debugが必要です。@quickcheck.report(property)— 例外を発生させる代わりに、構造化されたQuickCheckReportを返します。必要なのはArbitrary + Shrinkのみです。
test "reverse is an involution" {
@quickcheck.check((xs : Array[Int]) => xs.rev().rev() == xs)
}
derive(Arbitrary) と derive(Shrink) は、カスタム型のインスタンスを生成します。実行は省略可能なラベル付き引数で調整できます。count?、max_size?、max_shrinks?、seed?(失敗を再現するためのシード)、filter? と discard_ratio?、統計情報用の observe?(@quickcheck.{label, classify, collect} 経由)です。
ドキュメントテスト
.mbt.md ファイルまたは ///| インラインコメントで利用できます。
| コードブロック | 動作 |
|---|---|
```mbt check | LSP によるチェック |
```mbt test | test {...} として実行 |
```moonbit | 表示のみ(実行されません) |
例(インラインコメント):
///|
/// Increment an integer by 1
/// ```mbt test
/// inspect(incr(41), content="42")
/// ```
pub fn incr(x : Int) -> Int {
x + 1
}
リリース前チェックリスト
リリース前に実行します。
moon fmt # Format code (also migrates deprecated syntax and JSON config)
moon info # Generate type definition files
pkg.generated.mbti は moon info によって自動生成されます。直接編集しないでください。
組み込み型のメソッドを調べる
moon ide doc StringView # StringView methods
moon ide doc Array # Array methods
moon ide doc Map # Map methods
クイックリファレンス
| トピック | コマンド | 詳細 |
|---|---|---|
| テスト | moon test | https://docs.moonbitlang.com/en/stable/language/tests |
| スナップショットを更新 | moon test -u | 上記と同じ |
| テストを絞り込む | moon test --filter 'glob' | 特定のテストを実行 |
| ベンチマーク | moon bench | https://docs.moonbitlang.com/en/stable/language/benchmarks |
| ドキュメントテスト | moon check / moon test | https://docs.moonbitlang.com/en/stable/language/docs |
| フォーマット | moon fmt | moon.mod.json/moon.pkg.json を DSL に移行し、trait/impl に fn キーワードを追加します。落とし穴のリストにあるその他の項目は対象外です — try?、fn new、is-main、extend はすべて手動で対応します |
| 型を生成 | moon info | - |
| エラーコードを解説 | moon explain E0087 | または moon check --explain |
| ドキュメントリファレンス | moon ide doc <Type> | moon doc <SYMBOL> は非推奨です |
| レジストリを検索 | moon search <keyword> | 機械可読な出力には --json を指定します |
| レジストリツールを実行 | moonx author/mod/cmd/tool | 非推奨の moon runwasm に代わるコマンドです |
| 証明をチェック | moon prove | Why3 を別途インストールする必要はありません(v0.10.9) |
| ワークスペースを初期化 | moon work init | reference/configuration.md を参照してください |
| ワークスペースに追加 | moon work use mod1 mod2 | モジュールをワークスペースに追加します |
| API 使用状況を分析 | moon ide analyze . | 公開 API の使用状況統計を表示します |
moon ide ツール
コードナビゲーションでは grep より正確です。詳細は reference/ide.md を参照してください。
サブコマンドは peek-def、find-references、outline、doc、hover、
rename、analyze です。goto-definition はありません。
# Show symbol definition (global search by symbol syntax)
moon ide peek-def Parser::read_u32_leb128
# Package outline
moon ide outline .
# Find references
moon ide find-references TranslationUnit
# Peek a definition from a source location (contextual, substring match)
moon ide peek-def Parser -loc src/parse.mbt:46:4
# Show type signature and docs at a 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
moon ide analyze . # Current package
moon ide analyze internal/* # Glob pattern
関数型 for ループ
可能な場合は、関数型 for ループを優先してください。より読みやすく、動作を把握しやすくなります。
// 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 carrying extra loop state (v0.10.0)
for x in xs; sum = 0; sum = sum + x { ... }
// Infinite loop (for { } is deprecated)
for ;; { ... }
ループにラベルが付いている場合、その中の break / continue にもラベルを指定する必要があります。ラベル付きループ内でラベルなしのものを直接使うことは非推奨です。
ラベル付きブロック(v0.10.9)
ブロックにはラベルを付けられます。break label~ value は、その値を伴ってブロックを抜けます。関数から戻らずに、ネストされた制御フローから抜ける場合に使用します。
fn absolute(n : Int) -> Int {
result~: {
if n < 0 {
break result~ -n
}
n
}
}
ラベルなしの break でラベル付きブロックを抜けることはできません。また、continue の対象はループのみです。ブロックとループのラベルは同じ名前空間を共有します。外側のラベルを再利用すると、そのラベルがシャドーイングされ、E0036 が報告されます。
イテレーターとリスト内包表記
[for .. => body] は配列を構築します。フィルタリングするには、=> の前に if <guard> を追加します。
結果の型は期待される型に従います。デフォルトでは Array ですが、
FixedArray、ReadOnlyArray、String、Bytes、Json にもなります。
let evens = [for i in 0..<100 if i % 2 == 0 => i]
let labelled = [for i, x in ["a", "b", "c"] => "\{i}: \{x}"]
let text : String = [for x in 0..<3 => (x + 'a').unsafe_to_char()]
内包表記内では return / break / continue は使用できません。
明示的な [| .. |] リテラルで Iter を構築します(v0.10.4) — 期待される型によって
[x, ..xs] が Iter に変換されることに依存する方法は、評価順序を
暗黙のうちに変更するため、非推奨です。
let prefix = [| 1, 2, 3 |]
let values = [| ..prefix, 4, 5 |] // ..xs evaluated now, consumed lazily
let squares = [| for x in 1..<=3 => x * x |] // fully lazy
遅延シーケンスや無限シーケンスで状態を引き継ぐ場合は、Iter を直接構築します。
let mut p1 = 1
let mut p2 = 0
let fibs : Iter[Int] = Iter::new(fn() {
let next = p1
p1 = p1 + p2
p2 = next
Some(next)
})
let first_six = fibs.take(6).collect()
イテレーターは単一パスです。each / fold / collect で消費した後は、ソースから新しいイテレーターを取得してください。
テンプレート書き込み <+(v0.10.0)
buf <+ expr は StringBuilder に追加します。write_string より簡潔で、
マークアップや文字列の構築では $| 補間と相性がよく、buf <? expr は条件付きで書き込みます。
let buf = StringBuilder()
buf <+ "<ul>"
for item in items {
buf <+ $|<li>\{item}</li>
}
buf <+ "</ul>"
buf.to_string()
バイト列も補間をサポートします。let b : Bytes = b"value=\{x}"(UTF-8 エンコード)。
正規表現:=~、lexmatch、lexscan
必要な処理に応じて、次の3つの構文を使い分けます。
| 必要な処理 | 構文 |
|---|---|
| 1つの真偽値チェック | input =~ re"abc" |
| 異なる値を返す複数の正規表現ケース | lexmatch input with longest { ... } |
| カーソルを進めながらストリームをトークン化する | @lexbuf.{Lexbuf, AsyncLexbuf, StringScanner} に対する lexscan |
///| Boolean search (first-match, search-based — anchor with ^ / $ if needed)
let matched = s =~ re"abc"
///| Bind the match and the surrounding text
if s =~ (re"[0-9]+" as digits, before~, after~) { ... }
///| Multi-case dispatch over a String / StringView
let kind = lexmatch input with longest {
(re"^[ \t\r\n]+") => Space
(re"^[A-Za-z][A-Za-z0-9_]*" as t) => Ident(t)
_ => Other
}
右辺には正規表現の定数式(リテラル、const、または + / | /括弧で組み合わせた定数)を指定する必要があります。実行時文字列は拒否されます。
旧式の実験的な文字列ピース lexmatch パターンと lexmatch? は削除されました。lexscan は v0.10.9 で安定版になりました。
文字列定数
const は文字列の連結と補間をサポートします(v0.8.3 以降)。
const Hello : String = "Hello"
const HelloWorld : String = Hello + " world"
const Message : String =
$|========
$|\{HelloWorld}
$|========
エラー処理
MoonBit はチェック付きエラーを使用します。詳細は reference/ffi.md を参照してください。
宣言形式は3つあります。用途に合う最小のものを選んでください。
///| No payload at all (the common case for a single failure mode).
/// This declares an error type `EmptyInput` with one payload-free constructor
/// of the same name — `raise EmptyInput` works directly.
suberror EmptyInput
///| `derive` attaches to the payload-free form too, with no braces in between
suberror NotFound derive(Eq, Debug)
///| One constructor carrying a payload — the constructor repeats the type name
suberror DivError { DivError(String) }
///| Several constructors, like an enum
suberror ParseError {
InvalidEof
InvalidChar(Char)
}
可視性の指定は他と同様です。pub suberror / pub(all) suberror / priv suberror。
スナップショットを取る、または表示する可能性のあるエラー型には、すべて Debug を導出してください。 以下の catch 形式が返す Result はコンテナーなので、debug_inspect にはエラーを含む両方の側で Debug が必要です。
suberror ParseError {
InvalidEof
InvalidChar(Char)
} derive(Eq, Debug)
///| Declare with raise, auto-propagates
fn parse(s: String) -> Int raise ParseError {
if s.is_empty() { raise ParseError::InvalidEof }
...
}
///| Convert to Result — `try?` was removed (v0.10.0)
let result : Result[Int, ParseError] = Ok(parse(s)) catch { e => Err(e) }
///| Single expression with a fallback: omit `try`
let n = parse(s) catch { _ => 0 }
///| Panic instead of handling
let n = try! parse(s)
///| Full form: `noraise` branch handles the success value
let n = try parse(s) catch {
ParseError::InvalidEof => -1
_ => 0
} noraise {
v => v
}
この変換結果のスナップショットを取る場合(合成ケース):
test "parse failure" {
let r : Result[Int, ParseError] = Ok(parse("")) catch { e => Err(e) }
debug_inspect(r, content="") // moon test -u fills in Err(InvalidEof)
}
束縛に具体的なエラー型(Result[Int, Error] ではなく Result[Int, ParseError])を注釈すると、エラー側には Debug インスタンスがあり、スナップショットが機能します。単独の Error は、derive を付与できないオープン型です。独自の suberror にシグネチャを絞るか、エラーをマッチさせて分岐側のスナップショットを取ってください。
クリーンアップ:defer と errdefer(v0.10.9)
///| defer: always runs on leaving the body (return / break / continue / raise)
fn read_all(path : String) -> String raise {
let f = open(path)
defer f.close()
f.read_to_string()
}
///| errdefer: runs only when the body raises or is cancelled.
/// Use it when the resource is handed to the caller on success.
async fn connect(addr : Addr) -> Tcp {
let sock = make_tcp_socket()
errdefer sock.close()
connect_tcp_socket(sock, addr)
sock
}
連続する defer は逆順に実行されます。右辺では return / break / continue は使用できません。クリーンアップ自体がエラーを発生させる場合(非同期コードでは await する場合もあります)、そのエラーが元のエラーに置き換わります。エラーを発生させられない本体内の errdefer は E0091 を報告します。クリーンアップのみを行ってエラーを再発生させる catch-all は、errdefer に置き換える必要があります(E0092)。
Guard(v0.10.9)
guard は網羅性解析を実行します。失敗する可能性のある guard には else が必要です。コンパイラーが常に成功すると証明できない場合は E0087 を報告します。終了させることが意図されている場合は guard! を使用してください。guard! に else は指定しません。
///| Fallible guard: `else` supplies the value for the whole enclosing function
fn guarded_get(array : Array[Int], index : Int) -> Int? {
guard index >= 0 && index < array.length() else { None }
Some(array[index])
}
///| With a pattern, via `is`
fn plain_text(r : Resource) -> String raise Error {
guard r is PlainText(text) else { fail("not plain text") }
text
}
///| Provably total, or a deliberate panic: `guard!` takes no `else`
fn require_some(value : Int?) -> Int {
guard! value is Some(result)
result
}
網羅的な条件に対する冗長な ! や else は、警告されるようになりました。
CI と公開
サードパーティ製アクション
hustcer/setup-moonbit@v1 などのサードパーティ製アクションより、公式の curl インストーラー(assets/ci.yaml)または moonbit-overlay を使った Nix(assets/ci-nix.yaml)を優先してください。インストーラーは短く、アクションのサプライチェーンに何も追加しません。flake.nix が存在する場合、Nix の方法は完全に再現可能です。
moon update は必須です
ランナーは空のレジストリインデックスで起動します。レジストリでホストされている MoonBit 依存関係があると、moon update を実行するまで moon check / moon test / moon build は Failed to resolve registry dependency で失敗します。mooncakes を扱うすべてのワークフローで、CLI のインストール直後に実行してください。
ビルドが MoonBit に依存する npm パッケージの公開
プロジェクトによっては(Vite プラグインや言語ツールなど)、npm パッケージとして公開しながら、pnpm build 内で moon を実行します。たとえば、moon -C tools/parser build --release --target js を呼び出す pnpm build:parser などです。この場合、npm の公開ワークフローでは pnpm build の前に MoonBit CLI と moon update の両方をセットアップする必要があります。そうしないと、CI でビルドが失敗します。
OIDC Trusted Publishing(NPM_TOKEN 不要)を使用し、MoonBit を適切にセットアップする、リリースをトリガーとした最小限の公開ワークフローについては、assets/publish-to-npm.yaml を参照してください。手動での npm publish の実行を避けるため、リリース自動化ツールと組み合わせてください(release-please と OIDC のセットアップについては npm-release スキルを参照)。
mooncakes.io への公開
パッケージの内容は .moonignore(ない場合は .gitignore)でフィルタリングされます。
ドットで始まるファイルと _build/ はデフォルトで除外されます。mooncakes.io は、大文字と小文字だけが異なる既存のパッケージ名を拒否します。公開前にアーティファクトを確認してください。
moon publish --dry-run -v
unzip -l _build/publish/<author>-<module>-<version>.zip
アセット
assets/moon.mod— DSL 形式のモジュール設定サンプル(moon.mod.json の代替)assets/moon.pkg— DSL 形式のパッケージ設定サンプル(moon.pkg.json の代替)assets/ci.yaml— GitHub Actions CI(curl インストーラー)assets/ci-nix.yaml— Nix を使用した GitHub Actions CI(moonbit-overlay)assets/publish-to-npm.yaml— MoonBit ビルドと OIDC Trusted Publishing を使用した、リリースをトリガーとする npm 公開
Nix のセットアップ(moonbit-overlay)
moonbit-community/moonbit-overlay は、再現可能な MoonBit ビルドのための Nix フレークオーバーレイを提供します。
MoonBit プロジェクト用の最小限の flake.nix:
{
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}";
};
};
}
オーバーレイの主な API:
pkgs.moonPlatform.buildMoonPackage- MoonBit パッケージを Nix の派生物としてビルドしますpkgs.moonPlatform.bundleWithRegistry- キャッシュ済みレジストリを含む MOON_HOME を作成しますpkgs.moonPlatform.buildCachedRegistry- mooncakes レジストリの依存関係を事前に取得します
オーバーレイの
moonModJson引数は、引き続き JSON マニフェストを想定しています。 モジュールをmoon.modに移行した場合は、Nix 用に生成済みのmoon.mod.jsonを保持するか、 それを削除する前に、オーバーレイが DSL をサポートしているか確認してください。