LoginSignup
11
6

[バージョンアップに追従] Elixirから簡単にRustを呼び出せるRustler #1 準備編 [rustler 0.21.1]

Last updated at Posted at 2020-11-07

この記事は何?

rustler のバージョンが上がって Elixirから簡単にRustを呼び出せるRustler #1 準備編 のやり方では試せなくなったので、現在のバージョン (rustler 0.21.1) 向けの rustler の試し方を記載します。

基本的に元記事と同じ方法を辿るため、元記事の文章をコピペしています。ただしバージョンアップにより一部手順が異なります。

なお、rustler 0.21.x の API docs は生成が失敗しているので、代わりに rustler 0.22.0-rc.0 の docs を見ることをお勧めします。

(コピペ + 改変ここから)

導入手順

mix に rustler を追加

まずは rustler のパッケージをインストールして、mix rustler.new コマンドが実行できるようにします。
rustler 0.21.x に依存させるために、deps に {:rustler, "~> 0.21.0"} を加えてください。

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

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

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

$ mix rustler.new

Elixir 側の Module 名と Rust でのユニット名 (ファイル名) をそれぞれ対話形式で聞いてくるので、NifExample, example と入力します。
これで準備は完了です。

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

$ mix rustler.new
==> toml
Compiling 10 files (.ex)
Generated toml app
==> rustler
Compiling 5 files (.ex)
warning: EEx.eval_string/3 defined in application :eex is used by the current application but the current application does not directly depend on :eex. To fix this, you must do one of:

  1. If :eex is part of Erlang/Elixir, you must include it under :extra_applications inside "def application" in your mix.exs

  2. If :eex is a dependency, make sure it is listed under "def deps" in your mix.exs

  3. In case you don't want to add a requirement to :eex, you may optionally skip this warning by adding [xref: [exclude: EEx]] to your "def project" in mix.exs

  lib/mix/tasks/rustler.new.ex:79: Mix.Tasks.Rustler.New.copy_from/3

warning: Toml.decode!/1 defined in application :toml is used by the current application but the current application does not directly depend on :toml. To fix this, you must do one of:

  1. If :toml is part of Erlang/Elixir, you must include it under :extra_applications inside "def application" in your mix.exs

  2. If :toml is a dependency, make sure it is listed under "def deps" in your mix.exs

  3. In case you don't want to add a requirement to :toml, you may optionally skip this warning by adding [xref: [exclude: Toml]] to your "def project" in mix.exs

  lib/mix/tasks/compile.rustler.ex:177: Mix.Tasks.Compile.Rustler.check_crate_env/1

Generated rustler app
==> phx_rust_21
This is the name of the Elixir module the NIF module will be registered to.
Module name > NifExample
This is the name used for the generated Rust crate. The default is most likely fine.
Library name (nifexample) > example
* creating native/example/.cargo/config
* creating native/example/README.md
* creating native/example/Cargo.toml
* creating native/example/src/lib.rs
Ready to go! See /Users/koba_mac/srcview/phx_rust_21/native/example/README.md for further instructions.
koba_mac@kobas-MacBook-Air:~/srcview/phx_rust_21 $ 

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

native/example/src/lib.rs
use rustler::{Encoder, Env, Error, Term};

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

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

fn add<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> {
    let num1: i64 = args[0].decode()?;
    let num2: i64 = 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_21,
      version: "0.1.0",
      elixir: "~> 1.11",
      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_21 )です。
  • crate:はmix.exsのrustler_crates関数で定義された、NIF ID(前述)。
lib/example.ex
defmodule NifExample do
  use Rustler, otp_app: :phx_rust_21, crate: :example

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

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

Rust側のコード実装

テンプレートとして作られるコードを多少改変します。
以下を設定しています。

  • Elixir側のモジュール名
  • ElixirとRustの関数の関連付けリスト (elixir関数名, アリティ, Rust関数名)
native/example/src/lib.rs
...

mod atoms {
    // 生成されるコードでは rustler_atoms! となっているが、
    // cannot find macro `rustler_atoms` in this scope というエラーが出るため、絶対パスで参照するように変更する。
    rustler::rustler_atoms! {
        atom ok;
        //atom error;
        //atom __true__ = "true";
        //atom __false__ = "false";
    }
}

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

実行してみる

プロジェクトを起動すると、Rustのコンパイラーが走ります。
警告は出ますが、無事完了。

$ iex -S mix
Erlang/OTP 23 [erts-11.1.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Compiling NIF crate :example (native/example)...
   Compiling example v0.1.0 (/Users/koba_mac/srcview/phx_rust_21/native/example)
    Finished dev [unoptimized + debuginfo] target(s) in 4.39s
Interactive Elixir (1.11.2) - 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の関数を呼び出せてしまいました。

(コピペ + 改変ここまで)

まとめ

使用したコードは https://github.com/koba-e964/phx_rust_21/tree/intro に置いてあります。
次回のElixirから簡単にRustを呼び出せるRustler #2 クレートを使ってみるはまだ試していないため、この記事の続きが必要かどうかは未定です。

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6