2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
Rustでネイティブモジュールを書く(全1記事)
リンクをコピー
記事をブックマーク
Yukimasa Funaoka氏(以下、Funaoka):まず、自己紹介をします。私は、株式会社ディー・エヌ・エーでフロントエンドの業務をしている、船岡と申します。
本日は、どういった目的でネイティブモジュールを書くのか、具体的にRustで作るにはどうすればよいのか、そしてクロスプラットフォーム対応と、実際に書いてみて困ったことなどを紹介したいと思っています。
まず、ネイティブモジュールを書く理由ですが、ネイティブモジュールは、JS(JavaScript)の世界を超えて、ネイティブのバイナリとNode(Node.js)との架け橋になって、実行できます。
ネイティブモジュールのメリットとして、まず処理速度が挙げられると思います。
最近はesbuildが話題ですが、以前からあるものだと画像処理ライブラリのsharpなども、速度を重視したネイティブモジュールとして使われています。
次に、移植性です。C++で書かれたライブラリだと、JSで書き直すとけっこう大変ですが、ネイティブモジュールであれば、簡単にNodeから使えるというメリットがあります。node-sassは、内部的にはLibSassという、C++のライブラリを利用して書かれています。
最後に、シリアル通信のNode Serialportライブラリなど、OS準拠のネイティブからしか扱えない機能を利用できるというメリットがあります。
ほかには、GPUを使うケースや、JSから扱えない機能を使いたい時は、ネイティブモジュールを使う必要があります。
今回は、処理速度を上げるためのネイティブモジュールを書きました。実際のコードは出せませんが、代わりにサンプルを用意しています。RustのImageライブラリで、画像を読み込んで、rgbaのExcelデータを返すシンプルなものです。サンプルリポジトリと資料は、(スライドの)左下のURLにあります。
それでは実際に、napi-rsというライブラリを使って、Rustでネイティブモジュールを作っていきます。
以前のネイティブモジュールは、生のV8のコードを書く必要があり、Nodeのバージョンが変わるたびに使えなくなったりして、使いづらいものでした。
今では、名前が変わってNode-APIと呼んだりすることがありますが、これが後方互換のあるインターフェイスを実装しているため、すごく便利に使えようになりました。これをRustから作れるようにしたものが、napi-rsというライブラリです。
具体的には、ビルドや、各種ワークフローを実行するnapi/cli(Node.js)。Rustからnapiを使うためのcrate、Nodeでのパッケージに当たる、napiとnapi-deriveがあります。
シンプルな使い方としては、napi newを実行すると、initのように、さまざまなもののファイルが生成されます。このあたりはドキュメントを見れば、すぐにわかると思うので、今回は実際のコードでポイントになる部分を紹介したいと思います。
まず、Nodeのパッケージ実装に当たる、Cargo.tomlというファイルについて説明します。crate-typeは、ふだんRustではあまり書かないものだと思いますが、これを”cdylib”として動的リンクライブラリで、かつ他言語から扱えるようにビルドしています。
依存関係については、napiのバージョンのところで、featuresというオプションを指定できます。ここで、ターゲットになるnapiのバージョンや拡張機能を設定できます。
これについてはドキュメントがありますので、そちらを参照してください。
ライブラリ本体のコードには、napi_deriveにderiveという、#で書く部分の実装があります。これはjs_functionという関数定義で、括弧の数値は引数の数を示しています。実際に何個の引数で呼び出されたかは、コンテキストのlengthプロパティを参照することで確認できます。
もう1つが、module.exportsで、これはいわゆるJSでおなじみのmodule.exportsそのままです。wasmで書く時には、上のjs_functionを書くだけで済み、module.exportsを書く必要がありません。wasmで書く場合とnapiを使う場合ではちょっと違うため、忘れるという罠にハマりがちだと感じました。
あとは、napi buildというコマンドを叩けば、実際にNodeから使えるネイティブモジュールが完成します。基本的な作り方としては、以上です。
クロスプラットフォームの対応は、ネイティブモジュールはやはりネイティブバイナリなので、基本的にはビルドしたプラットフォームに依存します。例えば、Linuxの64bitでビルドしたものは、Linuxの64bitでしか使えません。
ただ、インストールのたびにビルドするのは時間がかかるし、そもそもビルド環境が整っていなければインストールすらできないこともあります。実際にnpmを実装した時に、エラーでこけるケースを体験された方も多いのではないでしょうか。
これに対する対処として、バイナリの配布があります。全部突っ込むというのは、node_modulesが太ってしまうし、あまりよくありません。
postinstallなどで、インストールする時にダウンロードする方法もありますが、ローカルでレジストリを運用していたり、インターネットを使ってはいけない環境だったりで、インストールできない場合があります。
そのほかに、optionalDependenciesを利用する方法がありますので、今回はこれを使いたいと思います。optionalDependenciesはインストールを試みて、失敗したらスキップするという挙動をします。
そのため、各プラットフォーム用のパッケージを並べておくことで、別のプラットフォーム用のパッケージがすべてスキップされて、今のプラットフォーム用のパッケージだけがインストールされる挙動になります。
ただ、これだけでは十分ではなくて、想定外のプラットフォームからインストールされることも考えられます。これに対しては、諦めてインストール時にビルドするという手もありますが、そのほかにwasmを用意しておいて、これにfallbackする方法もあります。ネイティブの機能を使う場合はできませんが、今回は後者で対応することにしました。
これをwasmで書くことで、ブラウザーで動くライブラリにできるので一石二鳥です。ネイティブに比べて速度面では劣りますが、ここは妥協します。
wasmの書き方については、今回Rustの話ということで割愛します。ただ、具体的にこれを実現するためには、ライブラリ本体とnapi用とwasm用の、3つのcrateを用意するのがシンプルです。
napiやwasmから、ライブラリ本体のcrateを呼び出す場合は、バージョンを書くところに相対パスでパスを指定します。それによってローカルのcrateが作成できるので、分割できます。
今回、napiをRustで書く際のnapi-rsの使い方、書き方がわからないため、とても困りました。サンプルコードがないし、なんならテストすらないし。一部のメソッドは、GitHub全体でコード検索しても、使用例が数えるほどしかないという、すごく悲しい状況になっています。
今回は、仕方なくソースコードを見ながら実装しましたが、napiを初めて触る人だと、けっこう大変かもしれないなと感じました。
crateがnot foundというエラーになってしまって、テストが書けないという問題も発生しました。これはcrate_typeをcdylibにしたことが原因で、Rust用のcrate_typeがない場合は、rustcが定義を見つけられないため、crateが見つからないというエラーになってしまいます。
これについてはRust用のライブラリのデフォルトである、rlibというcrate_typeを追加することで解決できます。
最後に、Error: Module did not self-registerというエラー。これはNode.jsからrequireした時に出るエラーですが、ググるとまったく別の要因のものばかり出てきて、すごくわかりづらいため、特定するのに非常に時間がかかりました。
これは先ほども述べましたが、module_exportsが足りていない場合に発生するエラーです。同じエラーでハマった人は、ぜひこれを参考にしてもらえればと思います。
最後にまとめです。Rustでネイティブモジュールを作るのは、現状けっこう大変だと感じました。C++で書くnapiは事例もあって、サンプルコードもそこらへんに転がっていてすごく楽なので、今書くのであればまだC++のほうがちょっと楽だと感じました。
ただ、最初に言ったように、ネイティブモジュールのメリットは非常に大きいものがあります。機会があればぜひ書いてみてもらいたいと思います。
以上で発表を終わります。ありがとうございました。
司会者:ありがとうございました。まだRustでnapiを書くこと自体を、やられている人は少ないのでしょうね。C++は、公式ドキュメントとかに、内容がありますが。
Funaoka:そうですね。
司会者:でも、非常にチャレンジングなことをされていておもしろかったです。もし機会があれば、Rustでネイティブバインドしたやつと、WebAssemblyにしたやつで、それぞれ試した時にどちらがどれだけいいとか、ビルド時間はどっちが速いかなども含めて、検証できるとおもしろいかもなと思いました。というか、やってみたいなと思いました(笑)。
Funaoka:そうですね。iswasmfastを見るとどっちが速いか、みたいなことをやっていますが、場合によるって感じですね。
司会者:実行時間でどっちが速いかも気になりますが、ビルド時間とかも含めると、どっちが速いのかなとかも気になっています。ありがとうございました。
Funaoka:ありがとうございました。
関連タグ:
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.12
今までとこれからで、エンジニアに求められる「スキル」の違い AI時代のエンジニアの未来と生存戦略のカギとは
PR | 2024.11.26
なぜ電話営業はなくならない?その要因は「属人化」 通話内容をデータ化するZoomのクラウドサービス活用術
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05