この記事は旧いので、@kobae964さんが書かれた [バージョンアップに追従] Elixirから簡単にRustを呼び出せるRustler #1 準備編 [rustler 0.21.1]をご覧ください

(この記事は、「Elixir or Phoenix Advent Calendar 2017」の20日目です)

前日は @tuchiroさんの 「ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる」のでした。
本日は、Elixir並列処理「Flow」の2段ステージ構造を理解するの続きです。「これまでGenStageやFlowで、並列データストリームを扱っていたのに、急にRustネタ!?」と思われるかも知れませんが、追々GenStageやFlowとの絡みが出てきます。

お知らせ
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
私もETSとFlowを交えた発表で参加します!
ストリームとデータ処理に興味ある方は、是非ともご参加下さい!

image.png

Rustler

RustlerはElixirからNIF経由で安全にrustのモジュールを呼び出すためのライブラリとmix拡張を含めたボイラープレートを作成するパッケージです。

数値・バイナリ・文字列に限らず、タプル・マップ・リスト等Elixir/Erlangのベーシックな型にはほとんど対応しています。

    • hex 公式ドキュメント

 

    Github Rustler

OSにRustのインストールが完了していることを前提して、本文を書いてます。


動機

Elixirで住所の正規化を行う案件が出てきたのですが、半角カナ変換を行うライブラリが見当たりませんでした。Erlang VMには苦手な処理に思え、当時検討したのがNIFによるネイティブコンパイラー系ライブラリの利用でした。

その当時ネイティブコンパイラー系の記事を探した時に、 @tatsuya6502氏の「ElixirからRustの関数をつかう → はやい」という記事を見つけて、「何とモダンな!Elixir+Rustの組み合わせは素晴らしい!」とは思ったのですが、「NIFって結構大変そうだな」という思いが脳裏に焼き付いていたので、結局Elixirでmojiexを自前実装しました。

その直後にRustlerを発見し、大変好感触でしたのでここでシェア致します。(GoやNimでも同等のものがないかを探したのですが発見できませんでした。)

半角カナ変換、ShiftJIS -> UTF-8変換等を今後扱っていきますのでお楽しみに!


導入手順

mixにrustlerを追加

まずはrustlerのパッケージをインストールして、mix ruslter.newコマンドが実行できるようにします。

# 通常通りelixirのプロジェクトを作成
$ mix new phx_rust
$ cd phx_rust
defp deps do
   [
    {:rustler, "~> 0.16.0"}
   ]
$ mix deps.get

rustlerのボイラープレートを生成

mixにrustlerの命令が追加されたので、さっそく使います。

$ mix rustler.new

Elixir側のModule名とRustでのユニット名(ファイル名)をそれぞれ対話形式で聞いてくるので

NifExample

example

と入力します。
これで準備は完了です。

ここで指定した「example」のAtom化した:exampleはRuslterの公式ドキュメント内ではNIF IDと呼ばれています。

/app/phx_rust # mix rustler.new
==> rustler
Compiling 2 files (.erl)
Compiling 6 files (.ex)
Generated rustler app
==> phx_rust
This is the name of the Elixir module the NIF module will be registered to.
Module name > Example
This is the name used for the generated Rust crate. The default is most likely fine.
Library name (example) > example
* creating native/example/README.md
* creating native/example/Cargo.toml
* creating native/example/src/lib.rs
Ready to go! See /app/phx_rust/native/example/README.md for further instructions.

このような流れでボイラープレートが出来上がります。
Rustのテンプレートはこちらになります。

    native/example/src/lib.rs
#[macro_use] extern crate rustler;
#[macro_use] extern crate rustler_codegen;
#[macro_use] extern crate lazy_static;

use rustler::{NifEnv, NifTerm, NifResult, NifEncoder};

mod atoms {
    rustler_atoms! {
        atom ok;
        //atom error;
        //atom __true__ = "true";
        //atom __false__ = "false";
    }
}

rustler_export_nifs! {
    "Elixir.NifExample",
    [("add", 2, add)],
    None
}

fn add<'a>(env: NifEnv<'a>, args: &[NifTerm<'a>]) -> NifResult<NifTerm<'a>> {
    let num1: i64 = try!(args[0].decode());
    let num2: i64 = try!(args[1].decode());

    Ok((atoms::ok(), num1 + num2).encode(env))
}

プロジェクト概要をmix.exsに定義

mix.exsに3箇所コードを追加します。

    1. compilers:行の追加

 

    1. rustler_crates:行の追加

 

    rustler_crates関数の追加
  def project do
    [
      app: :phx_rust,
      version: "0.0.1",
      elixir: "~> 1.4",
      compilers: [:rustler] ++ Mix.compilers, # 1. 追加
      rustler_crates: rustler_crates(),  #2. 追加
      start_permanent: Mix.env == :prod,
      deps: deps()
    ]
  end

  # 3.この関数(rustler_crates)を追加
  defp rustler_crates() do
    [example: [ # 呼び出し側Elixirモジュール NifExampleのマクロで使用するAtom
      path: "native/example",
      mode: (if Mix.env == :prod, do: :release, else: :debug),
    ]]
  end

Elixir側のコード実装

Elixirの呼び出し側モジュールを実装します。ElixirとRustのモジュールと関数を具体的にマッピングしていく作業になります。

use Rustlerでは、モジュールにrustler拡張を適用しています。
以下の2所の設定を、確認して下さい。

otp_app: はmix.exs内のproject[ app: ~ ]で指定してあるApp名。通常はプロジェクト名 (ここでは:phx_rust)です。

crate:はmix.exsのrustler_crates関数で定義された、NIF ID(前述)。

defmodule NifExample do
  use Rustler, otp_app: :app, crate: :example

  def add(_a, _b), do: exit(:nif_not_loaded)
end

ちなみにadd関数は最初からボイラープレートに含まれる関数です。

Rust側のコード実装

テンプレートとして作られるコードそのままです。
以下を設定しています。

    • Elixir側のモジュール名

 

    ElixirとRustの関数の関連付けリスト (elixir関数名, アリティ, Rust関数名)
rustler_export_nifs! {
    "Elixir.NifExample", // Elixirのmodule名
    [("add", 2, add)],    // Elixirとrustの関数の関連付け 
    None
}

実行してみる

プロジェクトを起動すると、Rustのコンパイラーが走ります。
警告は出ますが、無事完了。(unoptimized警告は重要ですが、次回に解説します)

$ iex -S mix

   Compiling syn v0.11.11
   Compiling rustler v0.16.0
   Compiling rustler_codegen v0.16.0
   Compiling example v0.1.0 (file:///app/{dir}/native/example)
warning: unused `#[macro_use]` import
 --> src/lib.rs:2:1
  |
2 | #[macro_use] extern crate rustler_codegen;
  | ^^^^^^^^^^^^
  |
  = note: #[warn(unused_imports)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 28.70 secs
Interactive Elixir (1.6.4) - press Ctrl+C to exit (type h() ENTER for help)

iexのプロンプトが起動するので、早速add関数を実行してみます。

iex(1)> NifExample.add(1,2)
{:ok, 3}

無事動作しました!

注目すべきは、戻りがタプルになっていることです。Rust側のタプルの構造がそのままElixirに返ってきてます。素晴らしいですね。

mix.exsの追加とexample.exを追加しただけで、rustの関数を呼び出せてしまいました。


終わりに

ElixirからRustの呼び出し。思った以上に簡単だったのではないでしょうか?次回は「Elixirから簡単にRustを呼び出せるRustler #2 クレートを使ってみる」です。

明日は @zacky1972 さんの「ZEAM開発ログv0.1.3 AI/MLを爆速にしたい! Flow のコードを OpenCL で書いてみる〜GPU編」です!

广告
将在 10 秒后关闭
bannerAds