結論: Vec<Term<'a>>
型にデコードするとよい
リスト全体は Vec<T>
型で受けるとして、T
になにを書けばよいのかしばし悩んだのですが、Erlant Term のままあつかうのでよければ Term<'a>
型で受ければよいということに気がつきました。
fn count<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> { let list: Vec<Term<'a>> = args[0].decode()?; Ok(list.len().encode(env)) }
各要素の具体的な値を利用したいばあいは、おのおの必要になった時点でデコードすればよさそうです。
実例
結論を書いてしまったので、以下は Rustler を使う手順の一般的な覚書です。
Rustler を導入する
Rustler は Rust で NIF を書くための便利なライブラリです。 上に書いたコードのように、とても簡単に Rust と Elixir をインタフェースしてくれます。
プロジェクトを作成し、依存するパッケージに Rustler を追加します。
$ mix new my_app $ cd my_app
defp deps do [ {:rustler, "~> 0.21"} ] end
パッケージを導入すると mix rustler.new
コマンドが利用できるようになります。
$ mix do deps.get, deps.compile $ mix help ... mix rustler.new # Creates a new Rustler project ...
NIF の雛形を生成する
追加された mix rustler.new
コマンドを使っって NIF の雛形を生成します。
モジュール名を訊かれるので入力します。
続けてライブラリ名を訊かれます。 モジュール名から生成した名前が示されるので、その名前でよければそのまま決定します。
native
という名前のディレクトリの下に指定したライブラリ名でパッケージが作成されます。
$ mix rustler.new This is the name of the Elixir module the NIF module will be registered to. Module name > MyApp.List This is the name used for the generated Rust crate. The default is most likely fine. Library name (myapp_list) > * creating native/myapp_list/.cargo/config * creating native/myapp_list/README.md * creating native/myapp_list/Cargo.toml * creating native/myapp_list/src/lib.rs
Rust のコードを編集する
native/myapp_list/src/lib.rs
を編集します。
ここではリストを受け取って要素の数を返す関数を書きます。
要素の数だけを知りたいので、今回は Erlang Term のままであつかい個々の要素はデコードしません。
このため先に書いたとおり、受け取った引数のリストを Vec<Term<'a>>
という型にデコードします。
use rustler::{Encoder, Env, Error, Term}; fn count<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> { let list: Vec<Term<'a>> = args[0].decode()?; Ok(list.len().encode(env)) } rustler::rustler_export_nifs! { "Elixir.MyApp.List", [ ("count", 1, count) ], None }
NIF をマウントする Elixir のモジュールを作成する
Rust のコードに書かれたモジュール名の Elixir のモジュールを作成します。
モジュールでは Rustler
を use し、オプションで OTP アプリケーション名と、Rust のライブラリ名を指定します。
defmodule MyApp.List do use Rustler, otp_app: :my_app, crate: :myapp_list def count(_list), do: :erlang.nif_error(:nif_not_loaded) end
コンパイラとパッケージを指定する
mix.exs
を編集してコンパイラに Rustler を追加し、利用するパッケージの指定を追加します。
def project do [ app: :my_app, version: "0.1.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, compilers: [:rustler | Mix.compilers()], # 追加 - コンパイラに Rustler を追加する rustler_crates: [myapp_list: []], # 追加 - myapp_list を利用するパッケージに追加する deps: deps() ] end
実行
実行します。 コンパイラに Rustler を追加しているので、Elixir のコードがコンパイルされるのと一緒に Rust のパッケージもコンパイルされます。
$ mix run -e 'MyApp.List.count([1, :ok, {}, [], "ABC"]) |> IO.inspect()' Compiling NIF crate :myapp_list (native/myapp_list)... Finished release [optimized] target(s) in 0.19s Compiling 2 files (.ex) Generated my_app app 5
いつか読むはずっと読まない:ラスト・モンスターに泣かされるレベル1パーティー
なけなしのお金で購入した金属製のアイテムをことごとく錆びつかせるラスト・モンスターが、キャリオン・クロウラーと並んで低レベルパーティーの憎き敵役だったのもよい思い出。