2024.12.10
“放置系”なのにサイバー攻撃を監視・検知、「統合ログ管理ツール」とは 最先端のログ管理体制を実現する方法
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.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
2024.12.09
10点満点中7点の部下に言うべきこと 部下を育成できない上司の特徴トップ5
2024.12.09
国内の有名ホテルでは、マグロ丼がなんと1杯「24,000円」 「良いものをより安く」を追いすぎた日本にとって値上げが重要な理由
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.12.10
職場であえて「不機嫌」を出したほうがいいタイプ NOと言えない人のための人間関係をラクにするヒント
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.06
嫌いな相手の行動が気になって仕方ない… 臨床心理士が教える、人間関係のストレスを軽くする知恵
PR | 2024.11.26
なぜ電話営業はなくならない?その要因は「属人化」 通話内容をデータ化するZoomのクラウドサービス活用術
2024.12.11
大企業への転職前に感じた、「なんか違うかも」の違和感の正体 「親が喜ぶ」「モテそう」ではない、自分の判断基準を持つカギ
PR | 2024.11.22
「闇雲なAI導入」から脱却せよ Zoom・パーソル・THE GUILD幹部が語る、従業員と顧客体験を高めるAI戦略の要諦