mcpField
M
moonbit-agent-guide

moonbit-agent-guide

github.com/moonbitlang開発7669 回インストール1 週間前に更新
リポジトリ

moonbit-agent-guide について

エージェントや開発者向けの MoonBit コーディングガイドで、プロジェクト構成、言語規約、テスト、コマンドラインの手順を扱います。IDE のセマンティックナビゲーションと、エージェント作成の自動化に MoonBit の `.mbtx` を使う点が特徴です。

主な機能

  1. 01MoonBit のモジュールとパッケージ構成を説明
  2. 02`moon ide` でシンボルをナビゲート
  3. 03言語とコード整理の規則を提示
  4. 04`.mbtx` で自動化を記述
  5. 05変更を検証・テスト・整形

活用シーン

  1. 01不慣れな MoonBit プロジェクトの構成を把握
  2. 02セマンティックナビゲーションで API を変更
  3. 03MoonBit 自動化スクリプトを作成・検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/moonbitlang/moonbit-agent-guide --skill moonbit-agent-guide

インストール後、Claude Code で/moonbit-agent-guideを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.gitignore
ide.md
LICENSE
moon.mod
moon.pkg
README.md
references/
advanced-moonbit-build.md
moon.pkg
moonbit-language-fundamentals.mbt.md

詳細

MoonBit タスクチェックリスト

  • 移行に関するアドバイスを適用する前に、moon version --all とプロジェクトの依存関係のバージョンを確認してください。このガイドは moon/moonc 2026-10-16(moonc v0.10.16)で検証されています。以下の非同期キャンセルに関するガイダンスは、moonbitlang/async 0.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. 1 つの大きなファイルよりも、役割が明確で小さなファイルを多数使ってください。

    • 関連する型や関数は、焦点を絞ったファイル(例:http_client.mbt、router.mbt)にまとめてください。
    • ファイルが大きくなりすぎたり、焦点がぼやけたりした場合は、新しいファイルを作成し、関連する宣言を移動してください。
  2. 同じパッケージ内であれば、宣言をファイル間で自由に移動してかまいません。

    • 各ブロックは ///| で区切られています。関数/構造体/トレイトの名前と pub の有無が同じであれば、ファイル間で移動しても意味は変わりません。各ブロックの順序も関係ありません。
    • パッケージ内でファイルを分割したり統合したりしても、安全にリファクタリングできます。
  3. ファイル名は整理のためだけに使います。

    • ファイル名がモジュールを定義すると想定してはいけません。また、型のパスにファイル名を使わないでください。
    • 型名を厳密になぞるのではなく、機能や役割を表すファイル名を選んでください。
  4. 新しいコードを追加する場合:

    • 機能に合った既存のファイルがあれば、そこに追加することを優先してください。
    • 適切なファイルがなければ、同じパッケージ内に説明的な名前の新しいファイルを作成してください。
    • 巨大な「impl」、「misc」、「util」ファイルを作成するのは避けてください。
  5. テスト:

    • 適切なパッケージ内にある専用のテストファイル(例:*_test.mbt)にテストを配置してください。 パッケージでは(*_test.mbtファイルに加えて)、*.mbt.mdファイルも Markdown ファイルに加えてブラックボックステストファイルとして扱われます。 トリプルバッククォートで区切られたコードブロックのうち、mbt check はテストケースとして扱われ、ドキュメントとテストの両方の役割を果たします。 mbt check のコード例を含む README.mbt.md ファイルを作成してもかまいません。また、GitHub との連携を改善するために、README.mbt.md から README.md へのシンボリックリンクを作成することもできます。
    • 小さなテストファイルを複数作成することも問題なく、推奨されます。
  6. インターフェースファイル(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 を追加するには:

  1. ディレクトリを作成します:./fib/

  2. ./fib/moon.pkg を追加します。

  3. コードを含む .mbt ファイルを追加します。

  4. 依存するパッケージでインポートします。

      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 のインポートブロックで個別にインポートする必要があります。

  1. moon add moonbitlang/async で依存関係を追加します。
  2. 実行可能ファイルの moon.pkg でパッケージの種類を宣言し、必要なものをインポートします。
    import {
      "moonbitlang/async",
      "moonbitlang/async/stdio",
    }
    pkgtype(kind: "executable")
    
  3. 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

コメント