独自のアプリケーションフレームワークからNode.jsへ Rustを使った実装方法
技術本部の川口です。フォルシアでは主にEC系の担当やアプリケーション基盤の開発・技術選定をしつつ、エンジニア採用やエンジニア広報なども(勝手に)やっています。 Rustのイベントを企画したりもしていますが、実は私自身は超ビギナーです...!
フォルシアはRustでインメモリデータベースの開発を行っており、すでに商用利用もしています(詳しくは『インメモリデータベースの開発言語にRustを選んだ理由』を参照)。
また、現在、アプリケーション基盤をフォルシア独自のアプリケーションフレームワークからNode.jsへ置き換えようというプロジェクトを行っています。社内の「もっとRust書きたいよね!」という声を受け、Node.jsでRustを使う方法について調べてみました。
この記事では2つの方法をご紹介します。
- FFI
- Neon
FFI
FFIとはForeign Function Interfaceのことで、ある言語から他の言語の関数を呼び出す方法を指します。今回はRustでダイナミックライブラリを作成し、それをNode.jsから呼びます。
Rust側の実装
新しいRustプロジェクトを作成します。
cargo new --lib greeting
--lib
を付けると、src/lib.rs
が生成されます。
Cargo.tomlには下記のように記載します。
[package] name = "greeting" version = "0.1.0" authors = ["your name"] [lib] crate-type = ["dylib"] [dependencies] libc = "0.2"
src/lib.rs
に対する設定は[lib]
のように書きます。また、crate-type = ["dylib"]
と指定することで、ダイナミックライブラリを作成できます。
libcはC互換の型を使えるようにするcrateです。
lib.rsに引数で受け取った名前の人にあいさつをする関数を書きます。
extern crate libc; use std::ffi::{CStr,CString}; #[no_mangle] pub extern "C" fn greet (arg1: *const libc::c_char) -> *const libc::c_char { let name = unsafe { CStr::from_ptr(arg1) }; let greeting = "Hello, ".to_string() + name.to_str().unwrap(); CString::new(greeting).unwrap().into_raw() }
returnが無くて落ち着かないのはRustビギナーあるあるでしょうか。。。
さて、これでcargo build --release
すると、./target/release/libgreeting.so
が生成されます(lib+プロジェクト名です)。
この.soファイルをNode.jsから実行しましょう。
JS側の実装
npm init
で プロジェクトを作成し、packege.jsonに"dependencies": {"ffi": "2.2.0"}
と記載してnpm install
。
(私の手元の環境ではこのままではnpm installがエラーとなりましたが、"ffi": "git://github.com/node-ffi/node-ffi.git"
と書き換えてやるとインストール出来ました。"2.2.0"でインストールできる人もいるようです。)
https://github.com/node-ffi/node-ffi
index.js
を作成します。
const path = require('path'); const ffi = require('ffi'); const libPath = path.resolve(__dirname, 'path/to/target/release/libgreeting'); const lib = ffi.Library(libPath, { greet: ['string', ['string']] }); console.log(lib.greet('Satomi'));
% node -e 'require(".")' Hello, Satomi
と表示できました!
FFIではlibcを使用してC互換の型が書けましたが、今回書きたいのはJavascriptですよね。
上記の方法でも頑張ればいろいろできるのですが...そのあたりを良しなにやってくれるライブラリ「Neon」を使ってみましょう。
Neon
公式のGetting startedの通りですが、やっていきましょう。
公式サイト:https://neon-bindings.com/
npm install -g neon-cli neon new greeting
とすると、下記のようなファイルが作られます。
% tree . ├── README.md ├── lib │ └── index.js ├── native │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── lib.rs └── package.json
package.json
を確認すると、npm install
にneon build
が紐付いているので、npm install
を実行すると、lib/index.js
にテンプレートが作られた状態になっています。
% cd greeting % npm install % node -e 'require(".")' hello node
こちらでも引数で受け取った名前の人にあいさつをする関数にしてみましょう。
#[macro_use] extern crate neon; use neon::prelude::*; fn greet(mut cx: FunctionContext) -> JsResult{ let x = "Hello, ".to_string() + &cx.argument:: (0)?.value(); Ok(cx.string(x)) } register_module!(mut cx, { cx.export_function("greet", greet) });
var addon = require('../native'); console.log(addon.greet('Satomi'));
と記載して再びnpm install
すると、
% node -e 'require(".")' Hello, Satomi
FFI同様にRustの関数をNode.jsから呼び出すことができました!
FFIとNeon
この記事を書きながら、上記のサンプルコードを作成していたのですが、FFIは(Rustビギナーの私にとっては)なかなか難しく、なんとか頑張って書いている途中でNeonを見つけました(笑)。もう少し踏み込んだ使い方をしてみないと分からない部分も多いですが、Neonのexamplesを眺めてみた限りでは、直感的に書けてとても良さそうです。
さいごに
Rust Advent Calendarには強者が勢ぞろいしていて、そこに肩を並べるのはかなり気が引けたのですが、ちょこちょこと書いてみました。もっと良い方法をご存知の方はぜひお教えいただければと思います...!
川口里美
2016年度新卒入社。技術本部 複数のEC系検索アプリケーションの開発や社内開発基盤を担当。