moonbit-agent-guide について
エージェントや開発者向けの MoonBit コーディングガイドで、プロジェクト構成、言語規約、テスト、コマンドラインの手順を扱います。IDE のセマンティックナビゲーションと、エージェント作成の自動化に MoonBit の `.mbtx` を使う点が特徴です。
主な機能
- 01MoonBit のモジュールとパッケージ構成を説明
- 02`moon ide` でシンボルをナビゲート
- 03言語とコード整理の規則を提示
- 04`.mbtx` で自動化を記述
- 05変更を検証・テスト・整形
活用シーン
- 01不慣れな MoonBit プロジェクトの構成を把握
- 02セマンティックナビゲーションで API を変更
- 03MoonBit 自動化スクリプトを作成・検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moonbitlang/ moonbit-agent-guide --skill moonbit-agent-guideインストール後、Claude Code で/moonbit-agent-guideを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitignoreide.mdLICENSEmoon.modmoon.pkgREADME.mdreferences/advanced-moonbit-build.mdmoon.pkgmoonbit-language-fundamentals.mbt.md詳細
MoonBit タスクチェックリスト
- 移行に関するアドバイスを適用する前に、
moon version --allとプロジェクトの依存関係のバージョンを確認してください。このガイドは moon/moonc 2026-10-16(moonc v0.10.16)で検証されています。以下の非同期キャンセルに関するガイダンスは、moonbitlang/async0.22 以降に適用されます。 - 編集する前に、外側の
moon.modと関連するmoon.pkgファイルを見つけてください。 moon ide docで API を確認してください。意味解析に基づくナビゲーションとリファクタリングには、outline、peek-def、find-references、hover、renameを使用してください。- エージェントが作成する自動化には
.mbtxのみを使用してください。自動化のロジックをシェルスクリプトやその他のスクリプト言語に含めないでください。 - 変更は適切なパッケージ内にとどめてください。ファイルは名前空間を定義しません。トップレベルの項目は
///|で分けてください。 - バグ修正には回帰テストを追加してください。新しい公開 API には、ブラックボックステストとドキュメンテーション文字列の例を追加してください。
moon checkと、関連する範囲を絞ったmoon testで検証してください。意図したスナップショット変更に限り、moon test --updateを使用してください。警告の一覧表示にはmoon explain --diagnosticを、選択した警告(たとえば、+unnecessary_annotation)の有効化には--warn-listを使用してください。- 引き継ぎ前に
moon fmtを実行し、moon checkによって生成されるpkg.generated.mbtiの変更を確認してください。特に、公開 API を安定させる必要がある場合は注意してください。 - 実行可能なドキュメントについては、警告を一括で抑制せず、すべての
.mbt.mdを--deny-warnを付けてチェックおよびテストしてください。例もフォーマットしてください。互換性のために受け入れられる構文が、必ずしも標準的な構文とは限りません。単独の Markdown は直接チェックおよびテストできますが、moon fmtにモジュールが必要な場合は、一時的なモジュール内でコピーをフォーマットし、結果を元に戻してください。
MoonBit プロジェクトの構成
MoonBit では、ソースコードファイルに .mbt 拡張子を、インターフェースファイルに .mbti 拡張子を使用します。
MoonBit プロジェクトの最上位には、プロジェクトのメタデータを指定する moon.mod ファイルがあります。プロジェクトには複数のパッケージを含めることができ、それぞれに独自の moon.pkg があります。サブディレクトリには、そのサブディレクトリで異なる依存関係のセットを使用できることを示す moon.mod ファイルが含まれる場合もあります。
レガシープロジェクトには、引き続き moon.mod.json が含まれている場合があります。これは古いモジュールメタデータ形式として扱い、新しい moon.mod.json ファイルを作成するのではなく、moon.mod への移行や更新を案内してください。
構成例
my_module
├── moon.mod # Module metadata; source option can specify the source directory
├── 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 `pkgtype(kind: "executable")`
├── 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)
-
モジュール:プロジェクトのルートディレクトリにある
moon.modファイルによって識別されます。 MoonBit の モジュール は Go のモジュールに似ており、サブディレクトリ内のパッケージの集まりです。通常はリポジトリまたはプロジェクトに対応します。 モジュールの境界は、依存関係の管理とインポートパスに影響します。 -
パッケージ:各ディレクトリにある
moon.pkgファイルによって識別されます。moonのすべてのサブコマンドは、引き続き現在のパッケージではなく、モジュールのディレクトリ(moon.modがある場所)で実行されます。 MoonBit の パッケージ は、実際のコンパイル単位(Go のパッケージに相当)です。 同じパッケージ内のすべてのソースファイルは一つの単位に連結され、そのパッケージ全体で定義を共有します。moon.modファイルのnameと、パッケージのソースディレクトリへの相対パスを組み合わせてパッケージ名が定義されます。ファイル名ではありません。 インポートで参照するのはモジュールとパッケージのパスであり、ファイル名ではありません。 -
ファイル:
.mbtファイルは、パッケージ内のソースコードの一部にすぎません。 ファイル名によってモジュール、パッケージ、名前空間が作られることはありません。 同じパッケージ内であれば、宣言を自由にファイル間で分割、統合、移動できます。 パッケージ内の宣言は、ファイルに関係なく、そのパッケージ内のほかの宣言を参照できます。
必ず従うコーディング/レイアウト規則:
-
1 つの大きなファイルよりも、役割が明確で小さなファイルを多数使ってください。
- 関連する型や関数は、焦点を絞ったファイル(例:http_client.mbt、router.mbt)にまとめてください。
- ファイルが大きくなりすぎたり、焦点がぼやけたりした場合は、新しいファイルを作成し、関連する宣言を移動してください。
-
同じパッケージ内であれば、宣言をファイル間で自由に移動してかまいません。
- 各ブロックは
///|で区切られています。関数/構造体/トレイトの名前と pub の有無が同じであれば、ファイル間で移動しても意味は変わりません。各ブロックの順序も関係ありません。 - パッケージ内でファイルを分割したり統合したりしても、安全にリファクタリングできます。
- 各ブロックは
-
ファイル名は整理のためだけに使います。
- ファイル名がモジュールを定義すると想定してはいけません。また、型のパスにファイル名を使わないでください。
- 型名を厳密になぞるのではなく、機能や役割を表すファイル名を選んでください。
-
新しいコードを追加する場合:
- 機能に合った既存のファイルがあれば、そこに追加することを優先してください。
- 適切なファイルがなければ、同じパッケージ内に説明的な名前の新しいファイルを作成してください。
- 巨大な「impl」、「misc」、「util」ファイルを作成するのは避けてください。
-
テスト:
- 適切なパッケージ内にある専用のテストファイル(例:
*_test.mbt)にテストを配置してください。 パッケージでは(*_test.mbtファイルに加えて)、*.mbt.mdファイルも Markdown ファイルに加えてブラックボックステストファイルとして扱われます。 トリプルバッククォートで区切られたコードブロックのうち、mbt checkはテストケースとして扱われ、ドキュメントとテストの両方の役割を果たします。mbt checkのコード例を含むREADME.mbt.mdファイルを作成してもかまいません。また、GitHub との連携を改善するために、README.mbt.mdからREADME.mdへのシンボリックリンクを作成することもできます。 - 小さなテストファイルを複数作成することも問題なく、推奨されます。
- 適切なパッケージ内にある専用のテストファイル(例:
-
インターフェースファイル(
pkg.generated.mbti)pkg.generated.mbtiファイルは、各パッケージの公開 API をまとめた、コンパイラーが生成するファイルです。 実装の詳細を含めずに、エクスポートされているすべての型、関数、トレイトを形式的かつ簡潔に示します。moon checkはデフォルトでこれらを生成し、コードレビューに役立ちます。公開 API に変更がないコミットでは、pkg.generated.mbtiファイルも変更されません。そのため、作業が完了したらpkg.generated.mbtiをバージョン管理に含めることを推奨します。 空白のみの整理も含め、pkg.generated.mbtiを直接編集しないでください。moon checkで再生成し、公開 API の変更を示す差分として確認してください。IDE のナビゲーションとシンボル検索のコマンドについては、下記の専用の
moon ideセクションを参照してください。
避けるべきよくある落とし穴
- 変数名や関数名に大文字を使わないでください - コンパイルエラーになります
- 変更可能なレコードフィールドには
mutを忘れずに付けてください - デフォルトでは不変です(通常、配列は変数への再代入を完全に行う場合を除きmutは不要です。たとえば、単純な push 操作にはmutは必要ありません) - エラー処理を無視しないでください - エラーを明示的に処理するか、呼び出し元で
raiseを宣言して、チェック対象のエラーを伝播させてください - 不要な
returnは使わないでください - 最後の式が戻り値になります - Type:: 接頭辞なしでメソッドを作成しないでください - メソッドには明示的な型の接頭辞が必要です
- トレイトの構文:トレイト宣言内では
fnを記述し、impl Trait for Type with fn method(...)の形式を使ってください。ドット構文でトレイト実装を公開するには、extend Type with Trait::{method}を明示的に使用してください。詳しくはreferences/moonbit-language-fundamentals.mbt.mdのメソッドの例を参照してください。 - 配列の境界チェックを忘れないでください - 安全にアクセスするには
get()を使用してください - 別のパッケージの関数を呼び出すときは、@package 接頭辞を忘れないでください
- ++ や -- は使わないでください(サポートされていません) -
i = i + 1またはi += 1を使用してください - エラーを伝播するために明示的な
tryを追加しないでください -raise関数内では、エラーを発生させる関数を通常どおり呼び出してください。ローカルで処理するにはcatchを使い、中断が意図されている場合にのみtry!を使用してください - レガシー構文:レガシーコードでは
function_name!(...)やfunction_name(...)?が使われている場合がありますが、これらは非推奨です。伝播には通常の呼び出しを使用してください。 mainに空のパラメーターリストを書かないでください -fn main { ... }またはfn main raise { ... }を使用し、fn main() { ... }やfn main() raise ... { ... }は使わないでください- レコード形式の列挙型やエラーコンストラクターのフィールドを書かないでください - ラベル付きコンストラクターフィールドには
label~ : Typeを使用します。例:InvalidNumber(input~ : String)。InvalidNumber(input: String)ではありません - C スタイルより範囲指定の
forループを優先してください -for i in 0..<(n-1) {...}やfor j in 0..=6 {...}のほうが MoonBit では慣用的です - 無限ループに
for { ... }を使わないでください - 代わりにfor ;; { ... }と書いてください - デバッグに
derive(Show)を使わないでください -Debugを導出し、テストや診断出力にはdebug_inspect()を使用してください(複合値を補間するには\{Repr(value)}を使います)。手動のimpl Showは、JSON、XML、ドメイン固有テキストなど、専用の表示形式に限定してください @json.inspect()を呼び出さないでください - パッケージ接頭辞なしで、プレリュードのjson_inspect(value, ...)を使用してください- JavaScript FFI:JavaScript の配列境界では
FixedArray[T]を使用してください。サイズ変更可能なArray[T]の JavaScript 表現は、安定した FFI 契約ではありません - 非同期 - MoonBit には
awaitキーワードがありません。追加しないでください。非同期関数はデフォルトでエラーを発生させるため、raiseを追加しないでください。非同期本体でエラーを発生させない必要がある場合にのみnoraiseを追加してください。 非同期関数やテストは、ほかの非同期関数を呼び出すものとして判定されます。 関数やテストを非同期として識別するには、async接頭辞を追加するだけです(例:[pub] async fn ...、async test ...)。
moon の基本
スクリプトモード(.mbtx)
エージェントが作成する自動化には、.mbtxのみを使用してください。ループ、
条件分岐、解析、変換、プロセスのオーケストレーションは、シェルスクリプトや
ほかのスクリプト言語ではなく、MoonBitで行います。外側のシェルは、
スクリプトの起動、または単一目的のコマンドの直接実行だけに使用してください。
.mbtxファイルは、任意のimport { ... }ブロックの後に、main
関数を含む通常のMoonBitコードを記述したものです。次のように直接実行します。
moon run path/to/script.mbtx [args...]
スクリプトで実際に使用する効果に応じて、mainを選んでください。
fn main { ... }は同期的で、エラーを伝播しません。fn main raise { ... }は同期的で、チェック対象のエラーを伝播することがあります。async fn main { ... }は、非同期APIを呼び出すスクリプトに使用します。"moonbitlang/async"をインポートしてください。raiseやawaitは追加しないでください。
3つの形式はすべて、デフォルトのWasmターゲットで動作します。純粋な同期スクリプトにasyncを指定しないでください。MoonBitからunused_asyncが報告されます。
自動化でコマンドを実行する場合は、"moonbitlang/async/shell"をインポートし、@shell.Cmdまたは@shell.Pipelineを使用してください。これらは実行ファイルと引数を分けて保持し、シェルを呼び出さないため、|、$()、*などの文字はそのまま渡されます。&&、シェルのループ、コマンド置換の代わりに、通常のMoonBitの制御フローを使用してください。
スクリプトモードはデフォルトでWasmを使用します。--wasm-policy policy.jsonを指定すると、MoonBitホストAPIに対するデフォルト拒否の制御が有効になります。必要なファイルシステム、環境、ネットワーク、プロセスへのアクセスのみを許可してください。プロセスを自動化する場合は、process.spawn: trueよりも、実行プログラムと引数のプレフィックスを正確に指定した、範囲の狭いprocess.allowルールを優先してください。このポリシーは子プロセスの実行を許可しますが、子プロセス自体をサンドボックス化するものではありません。そのため、信頼できない子プロセスは、ホスト側でも制限する必要があります。
最小限のスクリプト(hello.mbtx):
fn main {
println("Hello from MoonBit script mode")
}
コマンドライン自動化(sum_args.mbtx):
import {
"moonbitlang/core/env",
"moonbitlang/core/string",
}
fn main raise {
let args = @env.args()
for arg in args[1:]; total = 0 {
continue total + @string.parse_int(arg)
} nobreak {
println(total)
}
}
moon run sum_args.mbtx 10 20 12を実行すると、42が出力されます。
パッケージのインポートは、スクリプトヘッダーで直接使用できます(format_json.mbtx):
import {
"moonbitlang/core/json",
}
fn main raise {
let source =
#|{
#| "project": "moonbit",
#| "enabled": true
#|}
let value = @json.parse(source)
println(value.stringify(indent=2))
}
非同期自動化は、デフォルトのWasmターゲットで動作します。たとえば、
次を実行するにはmoon run async_job.mbtxを実行してください。
import {
"moonbitlang/async",
}
async fn main {
@async.sleep(10)
println("async automation complete")
}
基本コマンド
moon new my_project- 新しいプロジェクトを作成しますmoon run cmd/main- メインパッケージを実行しますmoon run - < hello.mbtx- 標準入力からスクリプトコードを実行します(手早く試すのに便利です)moon run -e "code snippet"- コマンドライン引数からコードを実行します(ワンライナーに適しています)moon run -e 'fn main { println("Hello, MoonBit!") }'moon build- プロジェクトをビルドします (moon runとmoon buildはどちらも--targetをサポートします。moon buildは--diagnostic-limit <N>もサポートします)moon check- ビルドせずに型チェックを行い、デフォルトでpkg.generated.mbtiファイルを生成します。高速なので、定期的に実行してください。 生成されたファイルを確認し、公開インターフェースに変更がないか確認してください。 (moon checkは--targetと--diagnostic-limit <N>もサポートします)moon check --target all- すべてのバックエンドを型チェックします シェルパイプラインや別のスクリプト言語ではなく、保存した.mbtxスクリプトで構造化診断を処理します。たとえば、以下をfilter_diagnostics.mbtxとして保存します。@shell.Cmd::each_lineはmoon checkを直接実行し、 JSON 出力をストリーミングして、その終了ステータスを返します。
import { "moonbitlang/async", "moonbitlang/async/shell", "moonbitlang/core/json", }
async fn main { let seen : Map[String, Unit] = Map([]) let exit_code = @shell.Cmd( "moon", ["check", "--target", "all", "--output-json"], ).each_line(line => { try @json.parse(line) catch { _ => () } noraise { { "level": "warning", "path": String(path), .. } => if !seen.contains(path) { seen[path] = () println(path) } _ => () } }) if exit_code != 0 { fail("moon check exited with code {exit_code}") } }
このコマンドプレフィックスを許可するポリシーで実行します。
```json
{
"process": {
"allow": [
{
"program": "moon",
"args_prefix": ["check", "--target", "all", "--output-json"]
}
]
}
}
moon run --wasm-policy moon-check-policy.json filter_diagnostics.mbtx
moon explain- コンパイラー診断と言語のトピックに関する組み込みドキュメントを表示します。moon explain --diagnosticは警告のニーモニックと ID を一覧表示します。moon explain --diagnostic 31は警告 31(unused_optional_argument)の説明を表示します。moon explain --diagnostic unused_optional_argumentは同じ警告の説明をニーモニックで表示します。moon explain --attributeは#deprecated、#alias、#cfg、#coverage.skip、#warningsなど、サポートされている属性を一覧表示します。moon explain --attribute deprecatedは#deprecated属性と、サポートされている形式の説明を表示します。
moon add package- 依存関係を追加しますmoon remove package- 依存関係を削除しますmoon fmt- コードをフォーマットします。定期的に実行してください。ファイルが書き換えられる場合があることに注意してください。 特定のディレクトリでコマンドを実行するには、moon -C dir checkも使用できます。
ホットパスのプロファイリング(moon run --profile)
moon run --profile --target native --release cmd/<main> は、サンプリングプロファイラーの下でネイティブのリリースビルドを実行し、自己時間と包含時間のランキング表に加えて、「MoonBit 呼び出し元に帰属するランタイムリーフコスト」のセクションを出力します(割り当て、参照カウント、文字列等価性のコストを、あなたの関数に対応付けます)。さらに、Instruments で開ける profile.json と .trace も出力します。macOS では Xcode の xcrun xctrace が必要なので、まずコマンドラインツールだけではなく、Xcode 全体をインストールしてください。単一の解析や計算はサンプリングするには短すぎるため、代表的なフィクスチャを使ってホットパスを数百回実行するループを main に指定してください。このループ用ハーネスは一時的なものであり、コミットしてはいけません。
自己時間でどの関数がサイクルを消費しているかを、包含時間でどの呼び出しサブツリーが支配的かを確認し、その後、短いサイクルで作業を進めます。プロファイルを取り、最上位の項目を修正し、再度プロファイルを取ります。差分を信頼する前に、必ずベースラインを取り直してください。サンプリングされた時間はマシンの負荷によって変動するため、以前のセッションの数値と比較するのではなく、ブランチと main を交互に続けてビルドし、ベンチマークしてください。
テストコマンド
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::*" // shortcut syntax
README.mbt.md 生成ガイド
- パッケージディレクトリに
README.mbt.mdを出力します。*.mbt.mdファイルとドキュメントコメントの内容では、mbt checkが特別に扱われます。mbt checkブロックはコードとして直接取り込まれ、moon checkとmoon testによって実行されます。コードスニペットをチェックしたくない場合は、明示的にmbt nocheckを使用することをおすすめします。 パッケージの型を参照するだけの場合は、構文ハイライトのみが適用されるmbt nocheckを使用してください。README.mdを想定しているシステムに対応するには、README.mbt.mdをREADME.mdにシンボリックリンクします。
テストガイド
動作が変わったときに簡単に更新できるため、スナップショットテストを使用します。
-
スナップショットテスト:
inspect(value)/debug_inspect(value)/json_inspect(value)を記述し、moon test --update(またはmoon test -u)を実行してcontent=を埋めます。Showを実装する値(プリミティブ、または手動でimpl Showした型)にはinspect()を使用します。Debugを導出するあらゆる型にはdebug_inspect()を使用します。独自のデータ型では、これがデフォルトです。- 複雑なネスト構造には
json_inspect()を使用します(ToJsonトレイトを使い、より読みやすい出力を生成します)。 - 関数の戻り値が大きすぎない場合は、戻り値全体を検査することをおすすめします。これによりテストがシンプルになります。
YourTypeに適宜Debugおよび/またはToJson(またはimpl Show)を導出します。
-
更新ワークフロー:出力に影響するコードを変更した後、
moon test --updateを実行してスナップショットを再生成し、テストファイルの差分を確認します(content=パラメーターは自動的に更新されます)。 -
検証の順序:
MoonBit Task Checklistに従ってください。 -
デフォルトではブラックボックステストにします。
@package.fnを介して公開 API のみを呼び出してください。プライベートメンバーが重要な場合に限り、ホワイトボックステストを使用します。 -
グループ化:関連するチェックを 1 つの
test "..." { ... }ブロックにまとめると、速度と明確さが向上します。 -
パニック:テスト名の先頭に
test "panic ..."を付けます。呼び出しが値を返す場合は、警告を抑制するためにignore(...)で囲みます。 -
エラー:成功が期待される場合は、エラーを発生させる関数を直接呼び出します。予期せず呼び出しがエラーを発生させた場合、実際のエラーとともにテストが失敗します。失敗が期待される場合は、
try ... catch ... noraiseを使用し、catchでエラーを検査して、noraiseで明示的に失敗させます。 失敗を期待する場合のデフォルトの形式:try f() catch { err => inspect(err) } noraise { _ => fail("expected to fail") }。
ドキュメントコメントのテスト
公開 API にはドキュメントコメントのテストを含めることをおすすめします。
///|
/// Return the sum of an `Array`.
///
/// # Example
/// ```mbt check
/// test {
/// inspect(sum_array([1, 2, 3, 4, 5, 6]), content="21")
/// }
/// ```
pub fn sum_array(xs : Array[Int32]) -> Int32 {
xs.fold(init=0, (a, b) => a + b)
}
ドキュメントコメント内の MoonBit コードは、型チェックとテストが自動的に行われます(moon test --update を使用)。ドキュメントコメントでは、mbt check に含めるのは test または async test のみにしてください。
仕様駆動開発
- 仕様は、スタブコードを宣言としてマークした読み取り専用の
spec.mbtファイル(名前は慣例であり、必須ではありません)に記述できます。
この意図的に不完全な契約は mbt nocheck で示されています。このガイドの一部としてチェックまたは実行する実装はありません。プロジェクトでは、実装が提供されるまで、moon check が未実装の宣言を報告します。
///|
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] によるコードナビゲーションとリファクタリング
プロジェクト内のシンボルやナビゲーションには、以下を使用します。
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]で、セマンティックな名前変更をプレビューします。ファイルを書き換えるには--applyを追加します。シンボル名が曖昧な場合は--locを優先してください。moon ide analyze [path]で、安全なリファクタリングを計画する際に、パッケージまたはモジュールの公開 API の使用状況を調べます。
これらのツールはトークンを節約でき、grep よりも正確です(grep は定義と呼び出し箇所の両方から、コメントも含めて結果を表示します)。
API の探索に使う moon ide doc
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" ...- 1 回の呼び出しで複数のクエリを実行し、結果をまとめます
- 例:
moon ide doc "String" "Array" "@json"で、複数の型とパッケージを一度に探索します
-
グロブ:部分一致には
*ワイルドカードを使用します。例:moon ide doc "String::*rev*"で、名前に「rev」を含むすべての String メソッドを検索します
moon ide doc の例
# search for String methods in standard library:
$ 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
# ... omitted ...
$ moon ide doc "@buffer.new" # list the specific function in a package:
package "moonbitlang/core/buffer"
pub fn new(size_hint? : Int32) -> 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) -> Int32?
Returns ... omitted ...
ベストプラクティス:このセクションはコマンドリファレンスとして扱い、検証については MoonBit Task Checklist で定義されている内容に従ってください。
moon ide rename sym new_name [--loc filename:line:col] の例
ユーザーが「関数 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: Int32, b: Int32) -> Int32 {
+pub fn calculate_sum(a: Int32, b: Int32) -> Int32 {
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 の例
ユーザーが「hover.mbt の 14 行目にある filter のシグネチャとドキュメント文字列は何ですか?」と尋ねた場合:
$ moon ide hover filter --loc hover.mbt:14
test {
let a: Array[Int32] = [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 ...
}
moon ide peek-def sym [--loc filename:line:col] の例
ユーザーが「Parser::read_u32_leb128 が正しく実装されているか確認できますか?」と尋ねた場合は、
moon ide peek-def Parser::read_u32_leb128 を実行して、定義のコンテキストを取得できます
(プロジェクト全体をセマンティックに検索するため、grep より優れています)。
L45:|///|
L46:|fn Parser::read_u32_leb128(self : Parser) -> UInt32 raise ParseError {
L47:| ...
...:| }
Parser 構造体の定義を確認するには、次を実行します。
$ 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 : Int32
| }
|
--loc 引数では、行番号を正確に指定する必要があります。位置引数の Parser が位置の特定に役立つため、列番号はおおよそでかまいません。
「sym」がトップレベルシンボルの場合、位置は省略できます。
$ 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()
| }
パッケージシンボルの moon ide outline [dir|file] と moon ide find-references <sym>
moon ide outline を使うと、パッケージまたはファイルをスキャンしてトップレベルのシンボルを確認し、grep を使わずに使用箇所を特定できます。
moon ide outline dirは現在のパッケージディレクトリのアウトラインを表示します(ファイルごとのヘッダー付き)。moon ide outline parser.mbtは単一ファイルのアウトラインを表示します。 パッケージの概要をすばやく確認したい場合や、peek-defの前に適切なファイルを見つけたい場合に便利です。moon ide find-references TranslationUnitは、現在のモジュール内にあるシンボルへのすべての参照を見つけます。
$ moon ide outline .
spec.mbt:
L003 | pub(all) enum CStandard {
...
L013 | pub(all) struct Position {
...
$ moon ide find-references TranslationUnit
パッケージ管理
モジュールと依存関係の確認
moon search 'html markdown' --limit 5
moon view moonbitlang/async --versions
moon tree --json
moon tree --package --target native --json
search と view はレジストリを検索するため、プロジェクトの外部でも使用できます。tree は現在のプロジェクトで解決済みの依存関係を調べます。--package を指定すると、モジュールの依存関係だけでなくパッケージのインポートも表示します。古いサンプルのバージョンをコピーするのではなく、追加またはアップグレードする前にバージョンを確認してください。
依存関係の追加
moon add moonbitlang/x # Add latest version
moon add moonbitlang/[email protected] # Add specific version
依存関係の更新
moon update # Update package index
サードパーティーのソースの閲覧(moon fetch)
moon fetch <author>/<module>[@<version>] は、オフラインで閲覧できるよう、パッケージのソースを .repos/<author>/<module>/<version>/ にダウンロードします(例、内部実装、生成された .mbti)。これは moon.mod にパッケージを追加しません。その場合は moon add を使用してください。.repos/ を .gitignore に追加してください。
moon fetch moonbitlang/[email protected] # browse source/examples without taking a dependency
一般的なモジュール設定(moon.mod)
name = "username/hello"
version = "0.1.0"
readme = "README.mbt.md"
repository = ""
license = "Apache-2.0"
keywords = []
description = "..."
import {
"moonbitlang/[email protected]",
}
options(
// source: "src", // Optional; default is "."
"preferred-target": "native",
)
依存関係のバージョンを手動で編集するのではなく、moon add moonbitlang/[email protected] と moon remove moonbitlang/x を使って import ブロックを管理してください。
一般的なパッケージ設定(moon.pkg)
シンプルな moon.pkg
import {
"username/hello/liba",
"moonbitlang/x/encoding" @libb,
}
import {
"username/hello/test_helpers",
} for "test"
import {
"username/hello/internal_test_helpers",
} for "wbtest"
pkgtype(kind: "executable")
パッケージ全体が特定のバックエンドのみをサポートする場合は、トップレベルで supported_targets = "native" または別のターゲットセット式を使用してください。
supported_targets = "native"
pkgtype(kind: "executable")
パッケージはディレクトリごとに定義されます。moon.pkg ファイルがないパッケージは認識されません。
パッケージごとに pkgtype 宣言を1つ使用します。library(デフォルト)、main エントリポイント用の executable、または外部ホストが使用するエクスポート用の foreign_library です。これらは相互に排他的です。pkgtype(kind: "executable") は旧形式の options("is-main": true) に代わるもので、pkgtype(kind: "foreign_library") は旧形式の options(link: true) に代わるものです。バックエンド固有の options(link: { ... }) は引き続きリンカーの詳細を設定します。references/advanced-moonbit-build.md を参照してください。
パッケージのインポート(moon.pkg で使用)
- インポート形式:
"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では、テスト対象パッケージのエイリアスを明示的に使用します(@packagename.fn)。修飾なしのアクセスは互換性インポートに依存し、test_unqualified_packageを発生させます。_wbtest.mbtはパッケージのスコープを共有するため、プライベートな定義に直接アクセスできます。
例:
///|
/// In main.mbt after importing "username/hello/liba" in `moon.pkg`
fn main {
println(@liba.hello()) // Calls hello() from liba package
}
標準ライブラリの使用(moonbitlang/core)
moonbitlang/core モジュールは、moon.mod の依存関係に追加しなくても常に使用できます。通常の core パッケージでは、@utf8、@json、@strconv などのパッケージエイリアスを使うために、moon.pkg で明示的にインポートする必要があります。コンパイラーが core パッケージの不足または暗黙的な使用を報告した場合は、"moonbitlang/core/encoding/utf8" のようなインポートを追加してください。
パッケージの作成
. の下に新しいパッケージ fib を追加するには:
-
ディレクトリを作成します:
./fib/ -
./fib/moon.pkgを追加します。 -
コードを含む
.mbtファイルを追加します。 -
依存するパッケージでインポートします。
import { "username/hello/fib", }
conditional compilation、link configuration、warning control、pre-build commands などのより高度なトピックについては、references/advanced-moonbit-build.md を参照してください。
非同期 I/O
非同期プログラミングでは、コンパイラーのサポートと moonbitlang/async
ランタイムを使用します。ランタイムをインポートすると、async fn main はデフォルトの Wasm ターゲットとネイティブターゲットの両方で動作します。個々のホスト I/O API はターゲット固有の場合があります。moon ide doc "@async" を使用し、対象のターゲットで検証してください。
ユーザー向けのサブパッケージには、@async(タスク、タイマー、キャンセル)、
@async/aqueue、@async/fs、@async/shell(シェルを使用しないプロセスのオーケストレーション)、
@async/stdio、@async/websocket があります。
それぞれを moon.pkg または .mbtx のインポートブロックで個別にインポートする必要があります。
moon add moonbitlang/asyncで依存関係を追加します。- 実行可能ファイルの
moon.pkgでパッケージの種類を宣言し、必要なものをインポートします。import { "moonbitlang/async", "moonbitlang/async/stdio", } pkgtype(kind: "executable") async fn main(fn mainではありません)を定義します。構造化並行性を実現するには、with_task_groupを使って並行タスクを起動します。///| 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") }) }) }
- 非同期関数には、デフォルトでエフェクト
raisingが設定されています。async fn main { ... }またはasync fn f(...) { ... }と記述し、async fn main raise { ... }とは記述しないでください。 - 非同期関数本体でエラーを発生させてはならない場合に限り、
async fn f(...) noraise { ... }を使用してください。noraiseの非同期関数では、エラーをローカルで処理しない限り、失敗する可能性のある API を呼び出せません。
with_task_group の構造化並行性に関する契約:
with_task_groupが返るとき、グループ内で起動したすべてのタスクは終了しています。タスクは、自身が取得したリソースを必ずクリーンアップする必要があります。- 起動したタスクのいずれかが失敗した場合(
allow_failure=trueを指定せずに起動した場合)、グループ全体が失敗します。グループ内の他のすべてのタスクがキャンセルされ、エラーはwith_task_groupの外へ伝播します。 - キャンセルされたタスクは通常の失敗ではなく、他のタスクのキャンセルを引き起こしません。async 0.22 以降では、キャンセルは
suberrorではなくコンパイラーシグナルです。
キャンセルとクリーンアップ(async 0.22 以降)
catch はエラーを処理しますが、キャンセルは処理しません。すべての終了経路でクリーンアップするには defer を、エラーまたはキャンセル時にクリーンアップするには errdefer を使用してください。クリーンアップだけを目的とする catch-all は、これに合わせて書き換えてください。
noraise はキャンセル不可を意味しません。@async.sleep は noraise ですが、キャンセルされる可能性があります。nocancel は別のエフェクトです。キャンセル中でもクリーンアップを完了させる必要がある場合は、@async.protect_from_cancel を使用してください。TaskGroup::add_defer には nocancel コールバックが必要です。
明示的なキャンセル処理が必要な場合に限り、@async.handle_cancellation を使用してください。キャンセル時には None を返しますが、タスクのキャンセル状態はリセットされません。@async.is_cancellation_error は非推奨で、常に false を返します。with_cancellation_handler は handle_cancellation に置き換えられています。async 0.22 の移行に関する注意事項を参照してください。
spawn_bg / spawn のクロージャー構文:
- ✅
() => { ... }— 慣用的な形式です。非同期かどうかはコンテキストから推論されます。 - ✅
async fn() { ... }— 明示的な注釈で、アロー形式と同等です。 - ⚠️
fn() { ... }— 警告 [0027]deprecated_syntaxが発生します:「このfnは非同期ですが、asyncが注釈されていません」。使用しないでください。 - ❌
async () => ...、fn() async { ... }、fn(args) async { ... }— いずれも構文解析エラーになります。asyncはfnの前にのみ置けます。アローラムダの前や、パラメーターリストの後には置けません。
非同期テスト
非同期関数を呼び出すテストには async test を使います。テストを含むパッケージでは、テストモード用に moonbitlang/async をインポートしてください。テストで使用する非同期サブパッケージも、同じ for "test" ブロック内でインポートしてください。
import {
"moonbitlang/async",
"moonbitlang/async/stdio",
} for "test"
///|
async test "sleep completes" {
@async.sleep(1)
inspect("done", content="done")
}
awaitキーワードはありません(エラーを発生させる関数と同様です)。async test内では、非同期関数を通常どおり呼び出します。async testには、デフォルトで非同期のエラー発生エフェクトもあります。raiseは追加しないでください。- 非同期テストは、デフォルトで並列実行されます。各テストでリソースを分離しない限り、ポート、ファイル、環境変数、グローバルな可変状態を共有しないでください。
- 対象とするターゲットで
moon testを実行してください。テスト対象の API がネイティブ実行を必要とする場合にのみ、--target nativeを追加します。テスト名や非同期のスケジューリング動作を確認するには、moon test -vを使ってください。 README.mbt.mdとドキュメント文字列では、mbt checkブロックにasync testブロックを含められます。該当するテストモード用に、パッケージがmoonbitlang/asyncをインポートしていることを確認してください。
MoonBit 言語ツアー
基本事項
- 式指向:
if、match、ループは値を返します。最後の式が戻り値になります。 - デフォルトでは参照:配列/Map/構造体は参照を介して変更します。プリミティブ型の可変性には
Ref[T]を使います。 - ブロック:トップレベルの項目は
///|で区切ります。ブロック単位でコードを生成します。 ブロック(波括弧で囲まれた部分)内に空行を入れる場合は、空行の後にコメント行を追加してください(コメント文はあってもなくてもかまいません)。 - 可視性:
fnはデフォルトでプライベートです。pubでは、許可される範囲で外部からの読み取り/構築を可能にします。pub(all)では外部からの構築を許可します。 - 命名規則:値/関数には lower_snake、型/列挙型には UpperCamel を使います。列挙型のバリアントは UpperCamel で始めます。
- パッケージ:コードファイル内で
importは使いません。@alias.fnで呼び出します。インポートはmoon.pkgで設定します。 - プレースホルダー:MoonBit コードでは、未完成の実装を示す有効なプレースホルダーとして
...を使えます。 - グローバル値:デフォルトで不変であり、通常は型注釈が必要です。
- ガベージコレクション:MoonBit には GC があり、ライフタイム注釈も所有権システムもありません。
Rust とは異なり、F# と同様に、変数を再代入する場合にのみ
let mutが必要です。構造体のフィールドや配列/Map の要素を変更する場合には必要ありません。
MoonBit のエラーハンドリング(検査済みエラー)
MoonBit では、未検査の例外ではなく、検査済みのエラー発生関数を使います。すべてのエラーは Error のサブタイプです。また、suberror を使って独自のエラー型を宣言できます。
検査済みエラーは、呼び出し箇所ごとに明示するのではなく、関数シグネチャで追跡されます。エラーを発生させる可能性のある関数は、raise または raise SomeError を宣言します。呼び出し元がそのエラーを上位に渡すだけの場合、呼び出し元も互換性のある raise を宣言し、エラー発生関数を通常どおり呼び出します。
raise関数内での通常の呼び出し:エラーは自動的に伝播します。fn main raise { ... }は、エラーを伝播させる同期コマンドラインの動作確認や小さな例で有効です。非同期のエントリーポイントにはasync fn main { ... }を使います。非同期関数はデフォルトでエラーを発生させられます。suberrorのコンストラクターでは、ラベル付きペイロードにlabel~ : Typeを使います。呼び出しやパターンマッチではlabel=valueを使います。expr catch { ... }またはtry { ... } catch { ... }:明示的に処理します。try! expr:エラーが発生した場合に中断します。
伝播のために Swift 形式の try を追加しないでください。新しいコードでは、旧式の function_name!(...) または function_name(...)? 構文を使わないでください。
///|
/// Declare error types with 'suberror'
suberror ValueError {
ValueError(String)
}
///|
/// Tuple struct to hold position info
struct Position(Int32, Int32) 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) -> Int32 raise ParseError {
// 'raise' throws an error
if s is "" {
raise ParseError::InvalidEof(pos=position)
}
@string.parse_int(s) catch {
_ => raise ParseError::InvalidNumber(pos=position, s)
}
}
///|
/// Declare a specific error type when callers should handle it precisely
fn div(x : Int32, y : Int32) -> Int32 raise ValueError {
if y is 0 {
raise ValueError::ValueError("Division by zero")
}
x / y
}
///|
test "expected success calls directly" {
inspect(div(6, 3), content="2")
}
///|
test "expected failure handles the raised error" {
try div(1, 0) catch {
ValueError::ValueError(message) =>
inspect(message, content="Division by zero")
} noraise {
_ => fail("expected to fail")
}
}
// Three ways to handle errors:
///|
/// Propagate automatically
fn use_parse(s : String, position~ : Position) -> Int32 raise ParseError {
// This plain call is the correct propagation syntax.
// `try! parse_int(...)` would abort instead of propagating.
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) -> Int32 {
let x = try! parse_int("123", position~) // label punning
x * 2
}
///|
/// Handle with try-catch
fn handle_parse(s : String, position~ : Position) -> Int32 {
parse_int(s, position~) catch {
ParseError::InvalidEof(pos=_) => {
println("Parse failed: InvalidEof")
-1 // Default value
}
_ => 2
}
}
///|
test "error propagation and handling" {
let position = Position(0, 0)
inspect(use_parse("21", position~), content="42")
inspect(use_parse2(position~), content="246")
inspect(handle_parse("", position~), content="-1")
inspect(handle_parse("invalid", position~), content="2")
}
重要:エラーを発生させる可能性のある関数を呼び出すとき、エラーを伝播させるだけであれば、マーカーは不要です。コンパイラーが推論します。非同期関数は、明示的に宣言しなくても自動的にエラーを発生させられます。エラーの伝播のために、非同期関数に raise を追加しないでください。未処理のエラーを非同期関数が拒否する必要がある場合にのみ、noraise を追加します。
SubErrorType::_ as err を使うと、あるエラー型のすべてのコンストラクターにマッチし、Error 値をそのサブタイプに絞り込めます。
///|
fn is_parse_error(error : Error) -> Bool {
match error {
ParseError::_ as err => {
let _ : ParseError = err
true
}
_ => false
}
}
///|
test "matching an error subtype" {
assert_true(is_parse_error(ParseError::InvalidEof(pos=Position(0, 0))))
assert_false(is_parse_error(ValueError::ValueError("other")))
}
整数、文字、オーバーロードされたリテラル
MoonBit は Byte、Int16、Int32、UInt16、UInt32、Int64、UInt64 などをサポートしています。
新しい例では Int32 と UInt32 を使用してください。2026-10-16 のフォーマッターでは、引き続き使用できるエイリアス Int と UInt はこれらの名前に正規化されます。
型が分かっている場合、リテラルをオーバーロードできます。
///|
test "integer and char literal overloading disambiguation via type in the current context" {
let (int, uint, uint16, int64, byte) : (Int32, UInt32, 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())
assert_eq(uint, 1U)
assert_eq(int64, 1L)
assert_eq(byte, b'\x01')
let (a1, a2, a3) : (Int32, 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
assert_eq(a1, 98)
assert_eq(a2, 'b')
assert_eq(a3.to_int(), a1)
assert_eq(a4, b'b')
}
バイト(不変)
///|
test "bytes literals" {
let b0 : Bytes = b"abcd"
let b1 : Bytes = [0xff, 0x00, 0x01] // Array literal overloading
assert_eq(b1.length(), 3)
guard b0 is [b'a', ..] && b0[1] is b'b' else {
// Bytes can be pattern matched as BytesView and indexed
fail("unexpected bytes content")
}
}
配列(サイズ変更可能)
///|
test "array literals overloading: disambiguation via type in the current context" {
let (a0, a1, a2, a3) : (
Array[Int32],
FixedArray[Int32],
ReadOnlyArray[Int32],
ArrayView[Int32],
) = ([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[_]
assert_eq(a0.length(), 3)
assert_eq(a1.length(), 3)
assert_eq(a2.length(), 3)
assert_eq(a3.length(), 3)
}
文字列(不変の UTF-16)
s[i] はコード単位(UInt16)を返し、s.get_char(i) は Char? を返します。
MoonBit は文字リテラルのオーバーロードをサポートしているため、次のようなコードスニペットを記述できます。
///|
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" else { fail("unexpected string suffix") }
// 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 = '='
assert_false(s.get_char(0) == Some(eq_char))
// s[0] == eq_char // ❌ Won't compile - eq_char is not a literal, lhs is UInt16 while rhs is Char
// Use: s[0] == '=' or s.get_char(0) == Some(eq_char)
// Requires `"moonbitlang/core/encoding/utf8"` in `moon.pkg`.
let bytes = @utf8.encode("中文")
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
}
}
文字列補間と StringBuilder
MoonBit では文字列補間に \{} を使用します。カスタム型では、トレイト Show を実装する必要があります。
///|
test "string interpolation basics" {
let name : String = "Moon"
let config = { "cache": 123 }
let version = 1.0
println("Hello \{name} v\{version}") // "Hello Moon v1"
// ✅ Quoted map keys are allowed inside interpolation expressions.
println(" - Checking if 'cache' section exists: \{config["cache"]}")
let sb = StringBuilder()
sb <+ "[\{[ for x in [1, 2, 3] => "\{x}" ].join(",")}]"
inspect(sb, content="[1,2,3]")
let x = 42
let streamed = StringBuilder()
streamed <+ "hello \{x}"
inspect(streamed, content="hello 42")
}
\{} 内の式は単一行である必要があります。
入れ子になった補間と文字列リテラルはサポートされていますが、\{} 内での改行はサポートされていません。
ストリーミング補間用の <+ マクロと <? マクロ
文字列補間は、<+ を使って Logger/StringBuilder 形式のライターに直接ストリーミングするか、<? を使ってオプショナルなライター経由で条件付きでストリーミングできます。
writer <+ "hello \{x}"
writer <+ {"key1": value, "key2": value2}
lhs <? "hello \{x}"
lhs <? {"key1": value, "key2": value2}
これはライターへの呼び出しに展開されます。
writer.write_string("hello ")
writer.write(x)
writer.write_object_begin()
writer.write_object_field("key1", value)
writer.write_object_field("key2", value2)
writer.write_object_end()
if lhs is Some(l) { l <+ "hello \{x}" }
文字列リテラルのセグメントには write_string が使用され、補間式には write が使用されます。
<? では、None の場合は書き込みが行われず、Some(writer) の場合はラップされたライターに同じ <+ の展開が適用されます。
<+ と <? の右辺には、テンプレート文字列/複数行テンプレート文字列、またはマップオブジェクトリテラルを指定する必要があります。任意の式は指定できません。
展開はマクロ形式です。テンプレート文字列に対する write_string と write、およびマップオブジェクトリテラルに対する write_object_begin、write_object_field、write_object_end を、writer 型がどのように実装しているかによって動作が決まります。HTMLBuilder や JSONBuilder などの型は、同じ構文で異なるセマンティクスの補間とストリーミングをサポートできます。
MoonBit では外部型にローカルメソッドを定義できるため、パッケージ内で既存のライター型にこれらのローカルなライターメソッドを追加し、この構文に対応させることができます。
複数行文字列
///|
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")
}
マップ(可変、挿入順を保持)
///|
test "map literals and common operations" {
// Map literal syntax
let map : Map[String, Int32] = { "a": 1, "b": 2, "c": 3 }
let empty : Map[String, Int32] = Map([]) // Empty map
// From array of pairs
let from_pairs : Map[String, Int32] = Map::from_array([("x", 1), ("y", 2)])
assert_true(empty.is_empty())
inspect(from_pairs["x"], content="1")
// 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 : Int32 = map["a"] // value = 10
inspect(value, content="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")
}
}
ビュー型
重要な概念:ビュー型(StringView、BytesView、ArrayView[T])を使うと、元データをコピーせずに範囲への読み取り専用アクセスを提供できます。ビューを受け取るパラメーターには、暗黙の変換によって対応する String、Bytes、または配列を渡すこともできます。逆方向の変換では、ビューを明示的に実体化する必要があります。
配列とバイト列のスライスでは、範囲もクランプされます。[10, 20, 30][1:100] には 20, 30 が含まれ、末尾を超える位置から始まる範囲は空になります。無効な範囲でパニックを発生させるには exact_view を使用し、None を返すには get_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]などを使用
文字列の境界:オフセットは UTF-16 コード単位で数えます。無効な境界をどう処理するかに応じて、操作を選択してください。
s[a:b](clamped_view)は範囲外のオフセットをクランプし、サロゲートペアが分割されないよう境界を内側に調整します。例外やパニックは発生せず、範囲の開始位置が終了位置を超える場合は空のビューになります。s.exact_view(start=a, end=b)は厳密に有効な境界を要求し、それ以外の場合はパニックを発生させます。s.get_view(start=a, end=b)は境界が無効な場合にNoneを返します。
///|
test "string view boundaries and ownership" {
let s = "ab😀cd"
inspect(s[0:3], content="ab")
inspect(s[3:6], content="cd")
inspect(s[0:100], content="ab😀cd")
inspect(s[4:2], content="")
assert_true(s.get_view(end=3) is None)
let owned : String = s.exact_view(start=2, end=4).to_owned()
inspect(owned, content="😀")
}
ビューを使用する場面:
- 残余パターン(
[first, .. rest])によるパターンマッチング - メモリー割り当てのオーバーヘッドなしで関数にスライスを渡す場合
- 大きなシーケンスの不要なコピーを避ける場合
.to_owned() を使用すると、StringView、BytesView、または ArrayView[T] を String、Bytes、または Array[T] として実体化できます。古いビュー変換メソッド .to_string()、.to_bytes()、.to_array() は非推奨です。(moon ide doc StringView)
ユーザー定義型(enum、struct)
///|
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[Int32]) -> Int32 {
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 : Int32
y : Int32
} derive(Debug, ToJson) // derive traits for Point
///|
pub fn Point::Point(x~ : Int32, y~ : Int32) -> Point {
{ x, y, }
}
///|
test "user defined types: enum and struct" {
let tree = Node(left=Leaf(1), 2, right=Leaf(3))
inspect(tree.sum(), content="6")
json_inspect(Point(x=10, y=20), content={ "x": 10, "y": 20 })
debug_inspect(
Point(x=10, y=20),
content=(
#|{ x: 10, y: 20 }
),
)
}
関数型の for ループ
///|
pub(all) enum SearchIndex {
Found(Int32)
InsertionPoint(Int32)
} derive(Debug, Eq)
///|
pub fn binary_search(arr : ArrayView[Int32], value : Int32) -> SearchIndex {
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 {
Found(i)
} else {
InsertionPoint(i)
}
}
}
///|
test "functional for loop control flow" {
let arr : Array[Int32] = [1, 3, 5, 7, 9]
debug_inspect(binary_search(arr, 5), content="Found(2)") // Array to ArrayView implicit conversion when passing as arguments
debug_inspect(binary_search(arr, 6), content="InsertionPoint(3)")
// for iteration is supported too
for i, v in arr {
println("\{i}: \{v}") // `i` is index, `v` is value
}
}
命令型ループよりも読みやすく、動作を把握しやすいため、可能な限り 関数型の for ループを使うことを強く推奨します。
ラベル付きパラメーターと省略可能なパラメーター
良い例:ラベル付きパラメーターと省略可能なパラメーターを使います
必須のラベルには label~ : T、デフォルト値付きのラベルには label~ : T = default、省略された引数を T? として表すには label? : T を使います。フォーマッターは、以前のデフォルト値付き表記 label? : T = default を label~ : T = default に書き換えます。
///|
fn g(
positional : Int32,
required~ : Int32,
optional? : Int32, // no default => Option
optional_with_default~ : Int32 = 42, // default => plain Int32
) -> String {
// These are the inferred types inside the function body.
let _ : Int32 = positional
let _ : Int32 = required
let _ : Int32? = optional
let _ : Int32 = optional_with_default
// `Repr` renders Option without relying on its deprecated `Show` implementation.
"\{positional},\{required},\{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(...) として値を渡す必要があります。
///|
fn with_config(a : Int32?, b : Int32?, c : Int32) -> String {
"\{Repr(a)},\{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~ : Int32? = Some(1) ではなく b~ : Int32 = 1 と記述します。
///|
fn f_misuse(a? : Int32?, b~ : Int32 = 1) -> Unit {
let _ : Int32?? = a // rarely intended
let _ : Int32 = b
}
// How to fix: declare `(a? : Int32, b~ : Int32 = 1)` directly.
///|
fn f_correct(a? : Int32, b~ : Int32 = 1) -> Unit {
let _ : Int32? = a
let _ : Int32 = b
}
///|
test {
f_misuse(b=3)
f_misuse(a=Some(5), b=2) // works but confusing
f_correct(b=2)
f_correct(a=5)
}
悪い例:arg : APIOptions(代わりにラベル付きの省略可能なパラメーターを使います)
///|
/// Do not use struct to group options.
struct APIOptions {
width : Int32?
height : Int32?
}
///|
fn not_idiomatic(opts : APIOptions, arg : Int32) -> Int32 {
opts.width.unwrap_or(arg) * opts.height.unwrap_or(arg)
}
///|
test {
// Hard to use in call site
inspect(not_idiomatic({ width: Some(5), height: None, }, 10), content="50")
inspect(not_idiomatic({ width: None, height: None, }, 10), content="100")
}
MoonBit パッケージ構成ガイドライン
パッケージは、コンストラクター、フィールド、パターンマッチング、メソッドをユーザーが使うことを想定した、公開具象型を所有するべきです。所有者はファサードパッケージ自体でも、ファサードが意図的に再エクスポートする非内部の公開パッケージでもかまいません。
型の所有権は、実装の配置よりも重要です。ユーザーがある型を @foo.X として扱うなら、X はパッケージ foo または foo が再エクスポートする公開パッケージで定義するべきです。特に、ユーザーが X::method を呼び出したり、レコードを構築したり、列挙型コンストラクターとマッチさせたり、生成された .mbti ドキュメントを参照したりする場合はそうです。
MoonBit では、非内部の公開パッケージから型が再エクスポートされている場合、メソッドの検索時にその型を所有するパッケージが暗黙的に読み込まれることがあります。たとえば、@foo が公開パッケージ @bar から type X を再エクスポートしている場合、外部ユーザーは値を @foo.X として記述しながら、@bar.X が所有するメソッドを呼び出せます。
実装を支える用途には internal/* パッケージを使います。
- スキャナー
- サブ構文のパーサー
- エスケープ/エンコードのヘルパー
- 検証用ヘルパー
- 低レベルアルゴリズム
- 非公開のヘルパー結果型
公開具象 API 型を internal/* に置き、再エクスポートによってファサードから完全な API を提供できると考えてはいけません。外部ユーザーは内部パッケージのメソッド所有者を同じように暗黙的に読み込めないため、x がファサードから再エクスポートされた型として型付けされていても、x.method() の呼び出しに失敗することがあります。また、コンストラクター、生成されるインターフェース、プライバシーの境界を把握しにくくなります。
using を正しく使う
pub using はファサードを使いやすくするために使い、型の所有権を移すためには使いません。
良い使い方:
// root package
///|
pub using @parser {parse, parse_fragment}
///|
pub using @dom {type Node, type NodeKind, to_markdown}
///|
pub using @serializer {type HtmlContext}
@parser、@dom、@serializer が、それぞれの API をすでに所有している公開パッケージである場合に適しています。
内部パッケージから値を再エクスポートする良い例:
///|
pub using @impl {decode_entities}
エクスポートする関数のシグネチャで内部型が公開されず、その値を意図的に公開 API にしたい場合は許容されます。
リスクのある使い方:
///|
pub using @internal_impl {type X}
公開具象型にはこれを避けてください。X が公開型なら、ファサードパッケージまたは非内部の公開パッケージで定義してください。X が本当に内部型であるなら、公開具象型として公開しないでください。
次のことが必要な場合は、pub using の代わりに明示的なラッパーを使います。
- 内部ヘルパーの結果を公開型に変換する
- 公開用のデフォルト値を適用する
- 内部ヘルパー型を隠す
- 公開 API の所有権を明確に保つ
.mbtiをレビューしやすくする
実践的なルール
公開関数が X を返し、ユーザーが X を調べたり、構築したり、パターンマッチングしたり、そのメソッドを呼び出したりする想定である場合、X はユーザーが指定するファサードパッケージ、またはファサードが再エクスポートする非内部の公開パッケージに属します。
ヘルパーパッケージが別のパッケージ用のデータを計算するだけなら、internal/* の下に置いてもかまいません。ただし、その型は内部に留めるか、公開ファサードを通じて公開しない単純なヘルパー結果型にしてください。
適切なパッケージ境界は次のようになります。
foo/
types.mbt // public Foo, FooMode, FooResult
api.mbt // public functions and Foo::methods
private_impl.mbt // private implementation files in same package
internal/foo/
scanner.mbt
escaping.mbt
validation.mbt