Rustでgemを書く際のハマりどころ in 2017

この記事は、Ruby Advent Calendar 2017の12/22の記事です。前日はTomoProgさんでした。

Rustという言語があります。 この言語の特徴は、一つは実行時のコストの低さ、もう一つは「所有権」システムにあると思います*1。所有権システムについては、2016年のRubyKaigiで発表されたGuildにインスピレーションを与えたことでもRuby界隈では知られているかと思います*2

が、今日の記事はRustについて記述することが目的ではないので、言語の特徴については細かくは触れないことにします。

RustによるRuby拡張の話題は、実はRubyKaigiでは2015年2017年の2度に亘ってセッションになっています。これは「型安全な」「実行が速い」言語で拡張を記述できるということに技術的関心が集まる、ということを表していると言えます。Rubyの特徴といえば「型がない」「実行が遅い」ですから、そうなるのも当然と言えるところはあると言えるでしょう。

というわけで実際にやってみたところ、そこそこハマりどころがあったので共有します。

TL;DR

  • helix公式サイトGetting Startedには何故かrailsの例しかない
  • dependencyに付け加えるべきはhelix_runtimeであってhelixではない
  • Rustのhelix crateとrubygemのhelix_runtimeはバージョンを合わせる必要がある
  • エントリポイントになるrubyファイルでバイナリをrequireする時にhelix_runtimeもrequireする必要がある

helix公式サイトのGetting Startedには何故かrailsの例しかない

helix公式サイトGetting Startedの例なんですが、

To start using Helix, add the helix-rails gem to your Gemfile:

とか

$ rails generate helix:crate text_transform

とか書いてあるわけですよ。私ゃgemが作りたいだけでrailsプロジェクトは要らんのだが、というツッコミは聞いてもらえません。まずここでハマりました。

幸い、gemを作るのに参考になるサイトも見つけることが出来たので、何とか参考にしてやってみることにします。

cf. https://blog.dnsimple.com/2017/05/writing-ruby-gems-with-rust-and-helix/

Dependencyに付け加えるべきはhelix_runtimeであってhelixではない

helixというgemがあるのですが、これはRust拡張を書くツールのhelixとは縁も所縁もないライブラリ*3です。最初これでハマりました。

もっとも、前掲のサイトには

spec.add_dependency "helix_runtime", "~> 0.5.0"

という記述はあったので、これを見落とした私が完全に悪いですね。しかし、_runtime という名のgemがbuild時に要るという発想は無かったので見逃していました。思い込みはよろしくありませんね。

Rustのhelix crateとrubygemのhelix_runtimeはバージョンを合わせる必要がある

(crateというのは、rubyでいうとgemに当たるような、ライブラリパッケージのことです。これの管理およびビルドへの利用には、Cargoというツールを用いますが、ここでは深くは触れません)

こう書いてみると当たり前のことではあるのですが、最初、gemspecには

spec.add_dependency 'helix_runtime', '~> 0.5.0'

と書きながら、ついCargo.tomlには

[dependencies]
helix = "*"

と書いてしまったばかりに動作しませんでした。

ちなみに、現時点での最新版は両方とも0.7.2ですので、そのように合わせたところ問題なく動作まで行うことができました。

エントリポイントになるrubyファイルでバイナリをrequireする時にhelix_runtimeもrequireする必要がある

C拡張の時と同様に出力されたバイナリをrequireする必要があります。これはC拡張と同じですので、出力されるパスがわかれば問題ありません。 出力先は、lib/[Cargo.tomlのpackageセクションのname属性で指定した名称]/native.[dylib|so]となります。

ですが、単にこれだけをロードすると未解決のシンボルの存在によりエラーになります。 これらの、helixを動作させるためのシンボルはどこで定義されているか、というとhelix_runtime*4です。 よって、コンパイルバイナリをロードする前にhelix_runtimeをロードする必要があります。

実際に動かしたコードについては追って載せます。

*1:Rustをある程度学んだ人は皆この概念をRustの特徴として挙げると言われるくらい際立った特徴

*2:cf. http://rubykaigi.org/2016/presentations/ko1.html

*3:Twistageという動画プラットフォームのクライアントアプリらしい

*4:正確にはその中のネイティブライブラリ