ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mruby/edge バージョン 1.0.3 の話【募集あり】

mruby/edge バージョン 1.0.3 を出した。

これは mruby/edge バージョン 1.0.0 ではないのかという話だが、ちょっと前に 1.0.0 を出したのだが、その時点では Hash と Object#== がなかったりしたので後から追加していたらバージョンを上げることになったため。

RubyKaigi 2024 時点からできるようになったこと

RubyKaigi 2024 の時点では怪しいハリボテのRubyで、ただ決まったコードをWASMに固められるという感じだったのだが、今ではかなりRubyの実装っぽくなっている。

まず、だいぶ設計が厳しかったので mruby/c 1.2 のコードを参照しながらVMを書き換えた。

udzura.hatenablog.jp

その上で以下のような内容を地道に地道に...。

  • クラスの定義ができるようにした
  • クラスでのインスタンス変数の利用ができるようになった
  • クラス定義で attr_(reader|writer|accessor) が使えるようになった
    • Cではやりづらい、ネイティブコードでの動的メソッド定義がRustでは普通にできるので天国のようです
  • グローバル変数の利用ができるようになった
  • ブロック引数の利用をサポートし、一部メソッドを実装した(Integer#times, Array#each)
  • ある程度正しく動くクロージャーを実装した。mruby/c では動かないケース(Qiitaの外部記事に飛びます)も動くようにしているぞ
  • WASM instance内部でVMインスタンスを保持するようにした。つまり状態を持てるようになった。今までは関数呼び出しごとにVMを初期化していた
  • オフセットを固定したメモリ領域をWASM外に公開できるようにした。 SharedMemory というクラスを導入している
  • メモリ上のオブジェクトやり取りがしやすいように、 Array#pack, String#unpack のうち最低限の実装をサポートした
  • 配列をサポートした
  • Hashをサポートした
  • == による比較が一通り動くようになった

まあ〜、まだできないことも多い。モジュールが実はないし、例外もない(例外は後から入れると大変らしいが...)。method_missingをはじめとした基本的なメタプロもあまりできない。そしてこれから話すように標準ライブラリは全然足りない。

今後について: ビッグなコントリビュートチャンス!

mruby/edgeはRubyとして動かす分に必要な基本的な命令とVMの機能を実装したつもりだが、いかんせん、標準ライブラリがない。

大体 mruby/c 1.2 ぐらいのライブラリを実装できていればいいかなと思っていて、そのメソッドを抜粋したのが以下のGistである。

とはいえ別に、例えば Time とか Random とかよく使うクラスを先に入れてもいいんじゃないかなと思っている。それらはmruby/cの用途では使わないというだけで、WASM(WASI)として使うことはあるだろうので。ちなみに、mruby/edgeはWASI有効でも無効でもビルドできるようにしています。

これらの実装は自分でもやっていくが、この記事で宣伝したいこととして、 コントリビュートを歓迎します ということがある。

つまり、 RustでRubyの標準ライブラリを実装するコントリビュートチャンス があるということなので、Rustが好きあるいは興味があり、Rustで抽象度の高いプログラミングを楽しみたい人にはこの上ない機会なんじゃ? と思われる。メソッド一つでもありがたいのでぜひ挑戦してみてほしい。

実装は prelude というモジュールの下に詰め込んでいるので参考にしてほしい。

github.com

integer.rs で実装している例を抜粋しておく。

pub(crate) fn initialize_integer(vm: &mut VM) {
    let integer_class = vm.define_standard_class("Integer");

    mrb_define_cmethod(vm, integer_class.clone(), "%", Box::new(mrb_integer_mod));
}

fn mrb_integer_mod(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
    let lhs: i64 = vm.getself().as_ref().try_into()?;
    let rhs: i64 = args[0].as_ref().try_into()?;

    Ok(Rc::new(RObject::integer(lhs % rhs)))
}

なんなら Copilot に書かせてもいいのではないだろうか。僕もだいぶ書かせてる。

ちなみにAPIドキュメントもまだないので補完で頑張るか、そっちも貢献していただけると嬉しい(頑張って書きます)。

テストも関数単位で書きやすいものは書いて欲しい(ここまで書いて、 mrb_integer_mod() にはテストがないことに気づいた。書きます)。

e2e、というか実際にスクリプトを評価するテストも書けるようにしている。ちなみにmrbcのソースコードをそのまま含んでコンパイルするcrateを作っているので、別途mrbcをインストールしなくてもOKになっている。

最後に期待値調整

RubyKaigi 2025 では別のプロダクトの話をします...。

2026 年にきっと...がんばります。