TypeScript はJS由来の言語仕様が根本的に不安定、Rust はアプリケーション層を書くのには低レベルすぎる、そんな不満はありませんか?
MoonBit はそういう不満を解決してくれる言語です。ただし、今はエコシステムの力を借りず、全部自力で書く前提ですが。
この記事は 2025/11 時点の MoonBit への所感になります。
https://zenn.dev/mizchi/articles/introduce-moonbit
2024/4 時点と比べて、ネイティブバックエンド対応、組み込みJSON型、例外のサポート、非同期サポートと大きく進化しています。
そろそろ実用できるんじゃないか?と思い、自分は MoonBit を使って React のバインディングを書いて、SPAとして動作するのを達成しました。その所感を含めての記事になります。
https://github.com/mizchi/js.mbt
MoonBit は自分がTypeScript に感じる不満の多くを解決しています。
生成コードが小さいのが自分にとって一番嬉しく、 npm に publish するライブラリを Moonbit で書くのも現実的です。
実際にいくつかサンプルコードでその嬉しさを説明します。
手元で動かしたい人は、インストールしてからmoon new myapp のようにボイラープレートを生成してから、試してください。
https://www.moonbitlang.com/download/
fnadd(a:Int, b:Int)->Int{ a+ b// 最後の式を return}// inline でも書けるテストtest"add test"{// 普通に書く場合assert_eq(add(1,2),3)// pipeline で書く場合、前の式を第一引数として渡す1|>add(2)|>assert_eq(3)}このコードは moon test で実行できます。--target でバックエンドの指定ができ、デフォルトだとmoon test --target wasm-gc 相当になります。
プロジェクトのデフォルトはmoon.mod.json のpreferred-target で指定できます。
// 構造体の宣言structPoint{ x:Int y:Int}derive(Show,Eq)// equal と to_string を自動実装// 関数宣言. a~ はキーワード引数で、 b?: はオプショナルなキーワード引数fnPoint::new(a~:Int, b?:Int=0)->Point{{a, b}// 推論込みで Point::{ a: a, b: b } のショートハンド}fnPoint::get_x(self:Self)->Int{self.x}test"test Point"{let p=Point::new(a=1)// inspect の content はインラインスナップショットで// moon test -u で反映できるinspect(Point::{a:1, b:0}, content="")}言語組み込みで最初からインラインスナップショットが組み込まれているのが快適です。
derive(Show) でPoint::to_string(self: Self) を実装することで、文字列としてマッチするinspect(value: &Show, content~: String) の引数を満たしています。
未使用コードの追跡も優秀で、この場合だとPoint::get_x に対して未使用の警告が出ます。
enumCounterAction{IncrementAdd(Int)}test"test match"{// 推論で Array[CounterAction]let actions=[CounterAction::Increment,CounterAction::Add(3)]// 明示的な Mutableletmut v=0// for loopfor actionin actions{// match 式なので値を返せる v+=match action{// マッチ時は CounterAction:: を省略できるIncrement=>1Add(n)=> n// 全てのパターンを網羅しているので、 unrechable 警告 _=>panic()}}assert_eq(v,4)}match式でパターンマッチで値を取り出しながら処理することができます。
エラーが発生する関数は明示的にそれを宣言して、呼び出す側が対応するエラーの raise を持たない場合、明示的に処理する必要があります。
// エラー型の宣言suberrorDivByZeroErrorfndiv(a:Int, b:Int)->Int raiseDivError{if b==0{ raiseDivByZeroError} a/ b}test"test error"{// noraise 関数では全てのエラーを処理しないといけないlet f:()-> noraise=()=>{// catch はパターンマッチlet _=trydiv(1,0) catch{DivByZeroError=>println("divide by zero error")}}f()}非同期が動作するコードは前提が多く大変なので割愛しますが、大体次のようなコードが書けます。
asyncfnfoo()->Unit raise{@async.sleep(1000)// await は不要}async 呼び出し側の async で非同期であることが判明するので、呼び出し側で await は不要です。raise 宣言は伝播します。
調べていてちょっとわかりづらかったのが、非同期関数で async fn(){} は デフォルトでraise Error (基底のError型をraise) 相当で、同期関数fn(){}は デフォルトではnoraise です。
最初は公式の Tour をやるのがいいでしょう。
網羅的ではありませんが、概要を掴むのに役立ちます。
https://docs.moonbitlang.com/en/latest/language/index.html
一番信頼できる情報源が Weekly Update と moonbitlang/core の実装です。
https://www.moonbitlang.com/weekly-updates/
ビルトインライブラリであるmoonbitlang/core は Moonbit 自身で書かれており、常に最新の言語仕様に追従しています。
https://github.com/moonbitlang/core
コンパイラの実装(OCaml)
https://github.com/moonbitlang/moonbit-compiler
moon の CLI 実装(Rust)
https://github.com/moonbitlang/moon
コア開発者らのパッケージである moonbitlang, bobzhang, tonyfettes, peter-jerry-ye, illusory0x0 氏らが比較的品質が高いです。
古いものは後方互換がなく、動かないまま放置されてる可能性が高いです。
MoonBit 自体の解説というより、コンピュータサイエンスを MoonBit で実践する記事が多いです。
https://www.moonbitlang.com/pearls/
extern を多用してバックエンドを切り替えてビルドしていると、LSP の動作が怪しくなるタイミングがありますf: () -> Unit raise? な関数をループ内で呼び出すとクラッシュするpub enum Option {...} のように、ビルトインと同名のシンボルを pub にすると、テストランナーがクラッシュするmoon.pkg.json を置いて制御します。ディレクトリ内の.mbt は同じ名前空間を持ち、ファイルスコープがありません。mizchi/js は@js.new_empty_object() のようにアクセスします。現在、ベータバージョンですが、まだ頻繁に言語仕様が追加・変更されています。
例えば例外宣言が()-> T!E が()->T raise E になり、fnalias @foo.bar as baz がusing 文の導入でusing @foo { bar as baz } になったりしています。
https://www.moonbitlang.com/weekly-updates/2025/08/11/index
ただ、これらは即座に廃止になるわけではなく、moon fmt でフォーマットするとある程度自動的に変換されるか、ビルド時の警告が出るようになり、一定の期間を経て廃止されます。
今予告されている大きな変更は、moon.pkg.json が moon.pkg の言語内 DSL になる、というものがあるのですが、これもフォーマッタで移行できるものだと思われます。
https://github.com/moonbitlang/moonbit-evolution/pull/17
自分が過去に見た中で一番激しかった変更が、 デフォルトの配列リテラルlet arr = [1,2,3]の推論結果がイミュータブルからミュータブルになったところだったのですが、流石にそれほど大きい変更は最近は見ていません。
とはいえ、基本的には実装から読み取ったドキュメントで明示されない振る舞いは自己責任で使うことになります。
公式ロードマップによると 2026年に 1.0 がリリースされる予定です。ここまで待つのもいいでしょう。
現代だと現実のアプリケーションを書くには非同期の対応が必要ですが、ここの仕様というより実装が安定していません。
現在、公式ライブラリのmoonbitlang/async が活発に開発されています。
これは native バックエンド前提のライブラリで、単なる非同期ユーティリティというわけではなく、Unix のシステムコールを低レベルからラップしたもので、 Rust でいう tokio に相当しているものになっています。
https://github.com/moonbitlang/async
これを何度か手元で試してみたのですが、触るたびにAPIが変わって、安定していません。
実験的な機能として、moon test は--target native かつ moonbitlang/async を依存に持つときだけasync test が使えるようになっています。
async test{@async.sleep(100)}また、maria というライブラリがあります。これは MoonPilotというAIコーディングエージェントを Moonbit で書き直しているバージョンで、ここでasyncとネイティブのドッグフーディングが行われて async 側に反映されているように見えます。
https://github.com/moonbitlang/maria
公式の X のアナウンスによると、--target js のasync test対応を進めているそうです。JSでバックエンドで非同期のコードを書いてる自分としては、実用にはこれを待ちたいところです。
仕様変更やエコシステムの変化による手戻りを厭わないなら、今からでも投資する価値がある言語です。
足りないのはエコシステムですが、これは結局鶏と卵なので、自分はJSバインディングを書きまくることで対応しつつ、流行るのを待つことにします。
自分は 2025年はAIでばかりコードを書いていましたが、Moonbit のおかげでプログラミングの楽しさを思い出せた気がします。あえて学習量が少なくAIが不得手な言語を書くことで、プログラミングの腕力を取り戻せたようにも思いますね。
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。
