FORCIA CUBEフォルシアの情報を多面的に発信するブログ

独自のアプリケーションフレームワークからNode.jsへ Rustを使った実装方法

2018.12.17

アドベントカレンダー2018

QiitaのRust Advent Calendar 2018 17日目の記事です。

技術本部の川口です。フォルシアでは主にEC系の担当やアプリケーション基盤の開発・技術選定をしつつ、エンジニア採用やエンジニア広報なども(勝手に)やっています。 Rustのイベントを企画したりもしていますが、実は私自身は超ビギナーです...!

フォルシアはRustでインメモリデータベースの開発を行っており、すでに商用利用もしています(詳しくは『インメモリデータベースの開発言語にRustを選んだ理由』を参照)。
また、現在、アプリケーション基盤をフォルシア独自のアプリケーションフレームワークからNode.jsへ置き換えようというプロジェクトを行っています。社内の「もっとRust書きたいよね!」という声を受け、Node.jsでRustを使う方法について調べてみました。

この記事では2つの方法をご紹介します。

  • FFI
  • Neon

FFI

FFIとはForeign Function Interfaceのことで、ある言語から他の言語の関数を呼び出す方法を指します。今回はRustでダイナミックライブラリを作成し、それをNode.jsから呼びます。

Rust側の実装

新しいRustプロジェクトを作成します。

  1. cargo new --lib greeting

--libを付けると、src/lib.rsが生成されます。

Cargo.tomlには下記のように記載します。

  1. [package]
  2. name = "greeting"
  3. version = "0.1.0"
  4. authors = ["your name"]
  5. [lib]
  6. crate-type = ["dylib"]
  7. [dependencies]
  8. libc = "0.2"

src/lib.rsに対する設定は[lib]のように書きます。また、crate-type = ["dylib"]と指定することで、ダイナミックライブラリを作成できます。

libcはC互換の型を使えるようにするcrateです。

lib.rsに引数で受け取った名前の人にあいさつをする関数を書きます。

  1. extern crate libc;
  2.  
  3. use std::ffi::{CStr,CString};
  4.  
  5. #[no_mangle]
  6. pub extern "C" fn greet (arg1: *const libc::c_char) -> *const libc::c_char {
  7. let name = unsafe { CStr::from_ptr(arg1) };
  8. let greeting = "Hello, ".to_string() + name.to_str().unwrap();
  9.  
  10. CString::new(greeting).unwrap().into_raw()
  11. }

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を作成します。

  1. const path = require('path');
  2. const ffi = require('ffi');
  3.  
  4. const libPath = path.resolve(__dirname, 'path/to/target/release/libgreeting');
  5. const lib = ffi.Library(libPath, {
  6. greet: ['string', ['string']]
  7. });
  8.  
  9. console.log(lib.greet('Satomi'));

  1. % node -e 'require(".")'
  2. Hello, Satomi

と表示できました!

FFIではlibcを使用してC互換の型が書けましたが、今回書きたいのはJavascriptですよね。
上記の方法でも頑張ればいろいろできるのですが...そのあたりを良しなにやってくれるライブラリ「Neon」を使ってみましょう。

Neon

公式のGetting startedの通りですが、やっていきましょう。

公式サイト:https://neon-bindings.com/

  1. npm install -g neon-cli
  2. neon new greeting

とすると、下記のようなファイルが作られます。

  1. % tree
  2. .
  3. ├── README.md
  4. ├── lib
  5. └── index.js
  6. ├── native
  7. ├── Cargo.toml
  8. ├── build.rs
  9. └── src
  10. └── lib.rs
  11. └── package.json

package.jsonを確認すると、npm installneon buildが紐付いているので、npm installを実行すると、lib/index.jsにテンプレートが作られた状態になっています。

  1. % cd greeting
  2. % npm install
  3. % node -e 'require(".")'
  4. hello node

こちらでも引数で受け取った名前の人にあいさつをする関数にしてみましょう。

  1. #[macro_use]
  2. extern crate neon;
  3.  
  4. use neon::prelude::*;
  5.  
  6. fn greet(mut cx: FunctionContext) -> JsResult {
  7. let x = "Hello, ".to_string() + &cx.argument::(0)?.value();
  8. Ok(cx.string(x))
  9. }
  10. register_module!(mut cx, {
  11. cx.export_function("greet", greet)
  12. });

  1. var addon = require('../native');
  2.  
  3. console.log(addon.greet('Satomi'));

と記載して再びnpm installすると、

  1. % node -e 'require(".")'
  2. Hello, Satomi

FFI同様にRustの関数をNode.jsから呼び出すことができました!

FFIとNeon

この記事を書きながら、上記のサンプルコードを作成していたのですが、FFIは(Rustビギナーの私にとっては)なかなか難しく、なんとか頑張って書いている途中でNeonを見つけました(笑)。もう少し踏み込んだ使い方をしてみないと分からない部分も多いですが、Neonのexamplesを眺めてみた限りでは、直感的に書けてとても良さそうです。

さいごに

Rust Advent Calendarには強者が勢ぞろいしていて、そこに肩を並べるのはかなり気が引けたのですが、ちょこちょこと書いてみました。もっと良い方法をご存知の方はぜひお教えいただければと思います...!

この記事を書いた人

川口里美

2016年度新卒入社。技術本部 複数のEC系検索アプリケーションの開発や社内開発基盤を担当。