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



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


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

前日は @tuchiroさんの [**「ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる」**](https://qiita.com/tuchiro/items/4ccba7e210c596c383af)のでした。
本日は、[**Elixir並列処理「Flow」の2段ステージ構造を理解する**](https://qiita.com/twinbee/items/43b767520da666ee960d)の続きです。「これまで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」**](https://fukuokaex.connpass.com/event/87241)を6/22（金）19時に開催します
私もETSとFlowを交えた発表で参加します!
ストリームとデータ処理に興味ある方は、是非ともご参加下さい！

[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://fukuokaex.connpass.com/event/87241)


## Rustler

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

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

- hex [公式ドキュメント](https://hexdocs.pm/rustler/basics.html)

- Github [Rustler](https://github.com/hansihe/rustler)


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

---

## 動機

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

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

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

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

---

## 導入手順



### mixにrustlerを追加

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

```bash
# 通常通りelixirのプロジェクトを作成
$ mix new phx_rust
$ cd phx_rust
```

```elixir:mix.exs
defp deps do
   [
    {:rustler, "~> 0.16.0"}
   ]
```

```bash
$ mix deps.get
```

---

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

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

```bash
$ mix rustler.new
```

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

> example

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

ここで指定した「example」のAtom化した`:example`はRuslterの[公式ドキュメント](https://hexdocs.pm/rustler/basics.html)内では**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

```rust
#[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``に３箇所コードを追加します。

1. compilers:行の追加
1. rustler_crates:行の追加
1. rustler_crates関数の追加

```elixir: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

  # ３．この関数(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拡張を適用しています。
以下の２所の設定を、確認して下さい。

- ``otp_app:`` は``mix.exs``内のproject[ app: ~ ]で指定してある``App名``。通常はプロジェクト名 (ここでは:phx_rust)です。
- ``crate:``はmix.exsのrustler_crates関数で定義された、NIF ID（前述）。


```elixir: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関数名)

```rust:native/example/src/lib.rs
rustler_export_nifs! {
    "Elixir.NifExample",　// Elixirのmodule名
    [("add", 2, add)],    // Elixirとrustの関数の関連付け 
    None
}
```


---

## 実行してみる


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

```bash
$ 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関数を実行してみます。

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

無事動作しました！

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

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


---
## 終わりに
ElixirからRustの呼び出し。思った以上に簡単だったのではないでしょうか？次回は[「Elixirから簡単にRustを呼び出せるRustler #2 クレートを使ってみる」](https://qiita.com/twinbee/items/54e8a4ec73bc27abd10e)です。


明日は @zacky1972 さんの[**「ZEAM開発ログv0.1.3 AI/MLを爆速にしたい！ Flow のコードを OpenCL で書いてみる〜GPU編」**](https://qiita.com/zacky1972/items/05ab840561657da1e154)です！