Help us understand the problem. What is going on with this article?

Elixirから簡単にRustを呼び出せるRustler #1 準備編

More than 1 year has passed since last update.

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

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

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

image.png

Rustler

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

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

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
mix.exs
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:行の追加
  2. rustler_crates:行の追加
  3. rustler_crates関数の追加
mix.exs
  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(前述)。
lib/example.ex
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関数名)
native/example/src/lib.rs
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編」です!

twinbee
フリーランスエンジニア Fukuoka.exでelixir強力推し 自社販売管理EPRパッケージによる基幹系ソリューションを手掛けてます Delphi / Oracle 案件納入歴 20年 フロントエンド vue.js / vuex / typescript サーバーサイドは Oracle / dot netcore C# / Elixir / Node.js
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away