大阪Ruby会議04でRustでRubyGemを書く話をしてきました

8/24に開催された大阪Ruby会議04に参加してました。

自分は地元が関西なのもあって相性が良いのか、大阪で開かれた地域Ruby会議ではよく登壇する機会があって、今回もそうなることができて良かったなと思っています。

今回の大阪Ruby会議はチーフオーガナイザーであるydahさんの趣味が出てる感じがして、めちゃくちゃ楽しいイベントでした。なんというかめっちゃRubyKaigiそのものっぽかった。

実は少し前に実家の母が倒れて入院したりといった事件があったので、お見舞のために奈良と大阪を往復したり、ホテルが1日ごとに変わって荷物がやたら多くなったりと結構バタバタしてしまって体力は消耗したんですが、結果的には無事に楽しむことができて安心しました。

セッション内容について

今回の両キーノートスピーカーである金子さんとはすみさんは、この二人なら信頼感しか無いなと思っていたのですが、期待以上に素晴らしい発表でした。

色々バタバタしてたのもあって金子さんのブログは事前に読めていなかったのですが、エディタとLSP周りは色々調べたことがあって、その延長でConcrete Syntax Tree (Lossless Syntax Tree)とSwiftやC#の関係についても少し触れたことがありました。前日の飲み会でその辺りの触りを聞くことができたので、色々心構えができて、楽しく聞くことができました。

はすみさんのキーノートはMatzスタイルにkakutaniさんが話しているかの様な良い話が組み合わさって、いかにもキーノートって感じで素晴らしかった。良い意味で「役に立たない」話って楽しいですよね、っていうのはRubyKaigiを始めとする技術者コミュニティではしばしば感じられる価値観で、自分もとても好きな考え方の一つです。自分もそういう風にネタを提供できたら良いなあと思いました。

キーノート以外で、今回めっちゃ良いなと思ったのはkoicさんのminifyrbの話です。Rubyをminifyするというのは現時点でそんなに実用性が無いことなんですが、そこからRubyの意味論の話に繋がって、そしてその上で自分がコミッタをやっているRubocopでソースコードを直すという完璧なマッチポンプに繋がっていました。しかもそのおかげでRubocopのエッジケースのバグが見つかって修正に繋がっている。楽しそうだから遊んでみて、結果OSSがちょっと便利になっているというのは本当に良い話だなと思いました。

自分の登壇について

発表資料はこちらです。 Rustで作るtree-sitterパーサーのRubyバインディング - Speaker Deck

作ったもの。 github.com

パーフェクトRubyという本を書いた時にC拡張の話を書いたことがあって、今だとRustでも書けるしやってみたいなあと思っただけというやつですが、色々と勉強になることがありました。

今回、細かいRustの解説をやっている時間がなかったので、Rustの知識がかなり前提になってしまう発表になってしまったのが反省点の一つです。なので、ここで少し補足を書いておきたいと思います。

RustでRubyGemを書くことの特徴の一つとして、メモリモデルの理解が必要になるという点が上げられます。

RustはGCが無い言語ですが、所有権システムとライフタイムによって必要なくなったデータを利用者が余り意識しなくても開放してくれます。また、基本的にデータはスタックに置くという考え方があると思っています。

スタックというのは雑に言えば、関数呼び出しをした時にその関数のために割り当てられる領域です。関数呼び出しから更に関数呼び出しを行うと、領域が積み上げられて終わったら結果を取り出して開放するという動きから積み重ねという意味でスタックと呼びます。

スタックは関数呼び出しが終わったらすぐに開放されます。例えばRubyでメソッドの中で定義したローカル変数はメソッドを抜けたら使えなくなりますが、Rustではもうちょっと広い領域で考える必要があります。

Rustの関数呼び出しの引数として所有権を持った型を受け取る様になっていると、その関数の呼び出しが終わった時に返り値としてその値を返さない限りはスタック開放時に一緒に受け取った引数も開放されて消滅します。所有権を引き渡すということは、基本的にはもう元の箇所で使わないという意志表示であり、関数の返り値になっていないということは関数の呼び出し元でも必要ないという意志表示です。なのでRustは必要なくなったデータを開放します。

そうなると困るデータを扱うために借用という概念を利用します。借用を利用すれば、データの所有権は元のスタックに残したまま、呼び出し先で値を参照したり、参照先のデータを書き換えたりできる様になります。

Rustで普通にコードを書いている時は、借用型やスタックに置いてるデータを意識することでデータの生存期間をコントロールできますが、RubyGemを書く時にいくつかの制約があってコントロールが難しくなります。

一番問題なのは、Rubyからのメソッド呼び出しの結果、いきなりRustの世界に入ってきて(その様に見える)その関数を抜けたらRustが理解しているスタックが消滅することです。

また、RustがアロケートしたデータをRubyオブジェクトとして利用したい時は、そのデータをRust側でヒープに置いてはいけないという制約もあります。これはRustからヒープに置いたデータをRuby側で認識できないので、そうしてしまうとメモリリークに繋がってしまうからです。これはつまり、Rust上でコードを書く時、Rubyのオブジェクトとして扱いたいデータをVec型などのヒープにデータ領域を確保する型に入れてはいけない、ということを意味します。これは結構面倒臭い。

という訳で、基本的にRustでアロケートした構造体などをRubyオブジェクトとして利用するためには、参照を受け取って所有権のある型を返すか、Rubyの管理しているヒープにオブジェクトとして存在していることが分かっているデータの参照を返す必要が出てきます。この辺のメモリの扱い方の関係でRubyGemとして利用できるメソッドシグネチャにはいくつかのルールがあります。その辺りのことはスライドにも書いてあるので一緒に参照していただけると良いかなと思います。

メモリの扱いに関する別の要素としては、RubyGCで不必要だと判断されたらRust側のデータも開放される様にライブラリが面倒を見てくれる様になってますが、Rustの世界での構造体同士の関係をRubyGCでに追いかけることはできないため、ちゃんと追いかけられる様にRubyオブジェクトとしての構成をコントロールするか(インスタンス変数に参照を割り当てておくとか)、RcやArc型などを使ってRust側でメモリが開放されない様な工夫をしないと、予期せぬ箇所でSEGVに繋がることがある、というハマりポイントがあります。

これは本質的にはRustに限った話ではなく、CでRubyGemを書く時もRubyGCが動く時に、実際にはどこを開放して良い領域なのかは考えておく必要があります。ただ、Cだとマニュアルで開放しなければいけないところを、Rustで書くとライブラリと所有権システムがかなり面倒を見てくれるので、楽な反面意識から抜けてしまうという印象がありました。 Rust自体はダングリングポインタなどが発生しにくい様に設計された言語ですが、RubyGCと組み合わさると普通にそういった事象が発生してしまうので、Rustを使っているからといって、それだけで安全になる訳ではないということですね。

何にせよ、GCはやっぱり難しいなと改めて認識できたのは今回の収穫と言えるかなと思います。

最後に、イベント後の懇親会でがちゃぴん先生に、「結局Rustで書くのってオススメなの?それがよく分からなかった」と言われて、確かにちゃんと書いてなかったなと思ったので、自分なりの結論を書いておこうと思います。

あくまで個人的な感想ですが、ネイティブ拡張を書きたいと思った時に、CとRustの習熟レベルが同じ程度ならRust使った方が多分楽だしオススメです。色々な便利ライブラリもCargoで管理してくれて使い易いし、文字列や配列状のデータの様によく扱う割にCだと面倒なことも上手くカバーしてくれますし、メモリ管理もCより確実に楽で安全です。一方でCなら何度も書いてきたし何とでもなるという人が無理してRust使っても、凄く楽になるかというとそうでもない気はします。自分はCもRustも大したことないので、勉強すること自体は色々あったけどRustの方が安全だし書き易いんじゃないかという感想でした。

まあそもそもの話ですが、CやRustでRubyGemを書くということ自体、そんなに頻繁に発生することではありません。今回題材にした様なCで提供されるライブラリのバインディングを書いたり、とにかくパフォーマンスを稼ぎたい時に必要に迫られたりといったケースでないなら、余り必要のない技術です。そういった意味では今回会場で発表を聞いてくれた皆さんの多くにとって、すぐには「役に立たない」話と言えますね。自分にとってさえ、明日仕事で使える様な話かというと全然そんな話ではありません。

しかし、いつ自分の様にやたら大量のデータをRubyで扱う様な仕事に就くか分からんもんです。自分も昔は全然想像してなかったことですが、そういう仕事をしてると、ここで何とかパフォーマンスを稼ぎたい、という状況が発生することがあります。そういった時に使える選択肢を増やしておくことは、すぐには役に立たなくてもいつか役に立つかもしれないなと思っています。

何年後かにそういう状況になったり、ふと思い出してRustの勉強をしてみたくなった時に、この記事と発表資料が力になると幸いです。

今回、登壇機会がいただけて、自分の学習にも色々繋げることができました。大阪Ruby会議04を開催してくれた皆様、一緒に参加してくれた皆様に、改めてお礼を申し上げます。

楽しいイベントをありがとうございました。またRubyコミュニティでお会いしましょう!