この記事は何?
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
...
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 のテンプレートはこちらになります。
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箇所コードを追加します。
-
compilers:
行の追加 -
rustler_crates:
行の追加 -
rustler_crates
関数の追加
...
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(前述)。
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関数名)
...
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 クレートを使ってみるはまだ試していないため、この記事の続きが必要かどうかは未定です。