実況: mutableなストレージのNIFsを実装してみる

#まえがき
どういう訳か小生の回り（電車で1時間30分の範囲）では、Elixirが熱い！そして Nervesが熱い！！

そんな訳でElixirを触ってはみるものの……組込系を生業としているが故に、Functional×パイプの特色にも、WebフレームワークのPhoenixにも、はたまたエコシステムのNervesHubにも、いまのところ「あ、そう。よかったね」程度の感想しか湧かないのである。Elixirの雰囲気を一通り味見した今、興味は専ら他言語拡張機能NIFs(Native Implemented Functions)に向かってしまうのである。

本稿は、そんな異端な記事なので、まっとうなElixir使いやよい子は真似しないように…

#お題目
さてNIFsで何を作ろうか?

ふむ、どうせ異端ならば、immutable/参照透過性を良しとする関数型言語の禁を破り、mutableなデータ構造fifo[*1]を実装してみよう:stuck_out_tongue_winking_eye:

```
= FIFOモジュールの仕様 =
    1. FIFO.create()        : 新たにfifoを一つ作り、そのハンドルを %FIFO{handle: h} で返す
    2. FIFO.front(%FIFO)    : fifoの頭の要素を覗き見て返す。fifoが空の場合はnilを返す
    3. FIFO.get(%FIFO)      : fifoの頭の要素を取り外して返す。fifoが空の場合はnilを返す
    4. FIFO.put(%FIFO, item): fifoのお尻にitemを追加する
    5. FIFO.count(%FIFO)    : fifoの中の要素の個数を返す
    6. FIFO.clear(%FIFO)    : fifoを空にする
```
こんなところかな…

[*1]実は、Erlangには double-ended fifo queueを実装した [queue](http://erlang.org/doc/man/queue.html)モジュールが用意されている。Elixirからは`:queue.ほにゃらら`で利用できるのだ。この queueの実装は「["Purely Functional Data Structures"](https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf) by Chris Okasaki」に載っているものと同等かな?

#めあて
日頃、息子たちに「過ぎ去った時間は、金をいくら積んでも取り戻すことができない」と口酸っぱく言っている手前、「NIFsを書いてみました。試してみました」と戯れただけに終わると示しがつかない。 オヤジの威厳を保つために、それなりの取り組み課題を設定しておこう:sweat_smile:

課題1. "Resource objects"の使い道の理解、C++でのコードの書き方の模索
課題2. thread safeなNIFsの書き方の模索
課題3. Elixirの任意のデータをNIFsで受け取って扱う方法の模索

#起: プロジェクトを用意する
まずは、いつもの様に mixでプロジェクトを用意する。

```bash:bash
> mix new fifo
> cd fifo
```

さて、NIFはC++で実装するので、C++のソースコードを入れておくディレクトリが必要になる。

Erlangの"Directory Structure Guidelines" [5] によると、ディレクトリ名は "cc_src"とするのが正統派の様だ。
しかし、いろいろと手抜きをしたいが故に Nervesの Community libraries / circuits_gpio [4] に倣って "src"としておく。

作成するNIFのC++ソースコードのファイル名は "fifo_nif.cc"としよう。

```bash:bash
> mkdir src
> touch src/fifo_nif.cc
``` 
"fifo_nif.cc"は、g++でコンパイル/リンクする。
今回は１ファイルだけなので、エイヤッと g++でビルドしても良いのだが、後学のために makeで行う。
mixから makeを呼び出すタスクは、既にHexに登録されていて [elixir_make](https://github.com/elixir-lang/elixir_make)だ。ありがたや:blush:
elixir_makeの Usageに従って、"mix.exs"の projectと depsに下記の設定を追加する。
そして、おもむろに ```mix deps.get```を実行して、elixir_makeをGitHubからダウンロードしておこう。

```elixir:mix.exs
  def project do
    [
       ︙
      compilers: [:elixir_make] ++ Mix.compilers,
       ︙
    ]
  end
   ︙
  defp deps do
    [
      {:elixir_make, "~> 0.4", runtime: false}
    ]
  end

```
```bash:bash
> mix deps.get
```
ふぅむ。【起】の最後の難関は Makefileの記述だ。

ここは手抜きをして、先出の circuits_gpioの Makefileを元に、必要な変更を加え、また不要な記述を削る方針で進める。

シンボル PREFIXと BUILDは、それぞれビルドで出来上がった動的ライブラリ(.so)と、その中間ファイル(.o)の格納先ディレクトリを表している。具体的には、

- PREFIX = _build/dev/lib/fifo/priv
- BUILD　= _build/dev/lib/fifo/obj

となる。これはそのまま頂こう。

動的ライブラリを作りたいので、gcc/g++ではコンパイラ・オプションに -fPIC、リンカ・オプションに -fPIC -sharedを指定するのはお約束。これらを シンボル CFLAGS, LDFLAGSの定義に追加する。

NIFsのビルドでは、Erlang由来のライブラリをリンクする必要はなく、ヘッダーファイル "erl_nif.h"さえ参照できれば良い。故に、シンボル ERL_LDFLAGSの定義の ```-L$(ERL_EI_LIBDIR)```は不要なのだが、実害は無さそうなので取り敢えず残しておこう。

その他、サフィックス .cを .ccに置換したり、 \$(CC)"を $(CXX)に置換したりして、以下の様になった。
ふぅ…

```makefile:Makefile
# Makefile for building the NIF
#
# MIX_APP_PATH  		path to the build directory
# ERL_EI_INCLUDE_DIR	include path to erlang header
# ERL_EI_LIBDIR			path to erlang/c libraries (Not necessaly for NIFs)

PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

NIF = $(PREFIX)/fifo_nif.so

CFLAGS  ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC
LDFLAGS += -fPIC -shared

# Set Erlang-specific compile and linker flags
ERL_CFLAGS  ?= -I$(ERL_EI_INCLUDE_DIR)
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)

SRC = src/fifo_nif.cc
HEADERS =$(wildcard src/*.h)
OBJ = $(SRC:src/%.cc=$(BUILD)/%.o)

all: install

install: $(PREFIX) $(BUILD) $(NIF)

$(OBJ): $(HEADERS) Makefile

$(BUILD)/%.o: src/%.cc
	$(CXX) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<

$(NIF): $(OBJ)
	$(CXX) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^

$(PREFIX):
	mkdir -p $@

$(BUILD):
	mkdir -p $@

clean:
	$(RM) $(NIF) $(BUILD)/*.o

.PHONY: all clean install
```

と言うことで、プロジェクトのディレクトリ構造はこうなる。

```
fifo/
├──_build/
│   ├── dev/
│   │   ├── lib/
│       │   ├── fifo/
│           │   ├── obj/    -- C++ソースをコンパイルしたオブジェクトファイル(.o)の格納先
│               └── priv/   -- C++ソースをコンパイル/リンクして出来た動的ライブラリ(.so)の格納先
├── deps/
├── lib/
│   └── fifo.ex   -- Elixir側のインターフェイス
├── src/
│   └── fifo_nif.cc   -- C++で記述したNIFのソースコード
├── test/
├── Makefile   -- C++をビルドする為のメイクファイル
├── README.md
└── mix.exs
```

#承: FIFO NIFの実装
本題である。
FIFOの実体はオーソドックスに STL std::dequeにする。
否、出来合いの std::dequeで手抜きしたいが故にわざわざ C++でNIFを記述することにしたのだ。

手抜きなので、当然 std::dequeの Allocatorはデフォルトのままで使う。C++11以降ではthread safeになっている様だし、たぶん大丈夫だろう。手抜きにはリスクが伴うものだ:stuck_out_tongue:

さて、FIFOの実体が std::dequeに決まったことで、考えるべきことが 2つある。FIFOの**(1)生成/破壊の制御**と**(2)thread safe化**だ。そう、「めあて」に掲げた課題1と課題2だ。

####(1)FIFOの生成/破壊の制御
std::dequeを使うには、その前にどこかでstd::dequeのインスタンスを生成しなきゃならない。今回は、Elixirのコードの中で自由に使いたいので、コードの文脈から独立した寿命を持つインスタンスが必要だ。つまり、ヒープメモリからアロケート(new)することになる…

おっと、厄介ごとの始まりだ。ヒープメモリからアロケートしたインスタンスは、要らなくなったら必ずデリートしなければならない。そうしなければ、メモリ・リークが発生して、遠い将来にシステムがハングアップするかも知れない。GCを持たないC/C++のエンジニア泣かせな面だ。

愚痴を言っても始まらないので、とっとと設計しよう。設計の選択肢としては２つある。

1. デリート用のNIF関数を用意し、Elixir側から明示的に不要になったFIFOをデリートする
2. なんらかの仕組みで不要になったFIFOを見つけ、それが自動的にデリートされるようにする

インプリメントが簡単なのは選択肢1だ。だが、依然としてメモリリークのリスクが残るうえに不細工。

では選択肢2は? "なんらかの仕組み"を Elixir/Erlangが提供してくれなきゃ始まらないが、果たしてそんな仕組みがあるのか?

…あった! "Resource objects"がそれだ。
Erlang erl_nifのリファレンスマニュアルの "Resource objects"のパラグラフにこうある。

> A resource type can have a user-supplied destructor function, which is automatically called when resources of that type are released (by either the garbage collector or enif_release_resource).

ふむふむ、NIFsのロード初期化関数load()の中で、"resource type"を enif_open_resource_type()で定義する。同時に destructorを登録か。こんな感じだな。

```C++:Resourceの使い方(その壱)
static static ErlNifResourceType* _ResType_DEQUE;  // resource type

void fifo_destroy(ErlNifEnv* env, void* ptr)
{
    MyDeque* deque_ptr = reinterpret_cast<MyDeque*>(ptr);
    deque_ptr->~MyDeque()   //インスタンスの破壊,メモリブロックの開放はElixir/Erlangが行う
}

int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
    _ResType_DEQUE = enif_open_resource_type(env, NULL, "myfifo", fifo_destroy, ERL_NIF_RT_CREATE, NULL);
    return 0;
}
```

一方で、インスタンスの生成は？

まず "resource type"を引数に enif_alloc_resource()でメモリブロックをアロケートする。そのメモリブロックに placement newで std::dequeをインスタンス化すれば良いかな。インスタンスの寿命をElixir/Erlangに委ねるために、enif_release_resource()で参照カウントを一つ減らしておくのを忘れずに。

```C++:Resourceの使い方(その弐)
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    MyDeque* deque_ptr
        = new(enif_alloc_resource(_ResType_DEQUE, sizeof(MyDeque))) MyDeque;  // placement new

    ERL_NIF_TERM term = enif_make_resource(env, deque_ptr);  // makeすると漏れなく 参照カウント=1 となる
    enif_release_resource(deque_ptr);  // 参照カウントを一つ減らす

    return term;
}
```

####(2)FIFOのthread safe化
残念ながら Elixir/Erlangは、Nifs そして"Resource objects"を thread safeには扱ってくれない。

以前、マルチメディア・ライブラリSMFL(OpenGL)の NIFsを実験的に書いたことがあるが、随分と痛い目にあった。Elixir/Erlangの最下層では、数個のthreadが並行に動いており、どのthreadから何時 NIFsがcallされるかは決まっていないのだ。

ならば NIFs側で自己防衛するしかあるまい。Mutexの出番である。

erl_nifのAPIにもMutexのオペレーションが用意されているが、STLの std::mutexに比べ少々扱いが面倒なので、お手軽な std::mutex & std::lock_guardを使うことにしよう。

Mutexによる排他制御が必要なコード・ブロックは、(1)std::dequeを破壊的に書き換えるコード塊と、(2)複数のstd::dequeメソッドをアトミックに実行したいコード塊だ。たとえばこう…

```c++:
ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
　　<略>
   // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    // empty(),front(),pop_front()をアトミックに実行
    if (!deque_ptr->m_deque.empty()) {
        ErlNifBinary bin = deque_ptr->m_deque.front();
        deque_ptr->m_deque.pop_front();
        <略>
    }
    <略>
}
```

####(3)データをNIFsで受け取る方法
ここまでで、なんとか課題を2つクリアできた:blush:

残るはあと一つ…「Elixirの任意のデータをNIFsで受け取って扱う方法」だ。

はっきり言ってこの課題は、小生の好奇心を満たすのが目的で、それ以上でもそれ以下でもない。超個人的な拘りに過ぎない:stuck_out_tongue_winking_eye:

さてと。Erlang erl_nifのリファレンスマニュアルによると、Elixir/ErlangからNIFs関数をcallする際、その引数データは ERL_NIF_TERM型に変換して渡される。NIFs側では、APIの erl_get_***関数群を使って渡された ERL_NIF_TERMから元のデータを復元して使用することになる。こんな感じだ…

```C++:erl_get_***の例
ERL_NIF_TERM foo(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    <略>
    double prm;
    if (!enif_get_double(env, argv[0], &prm)) {
        return enif_make_badarg(env);
    }
    double twice = 2.0 * prm;
    <略>
}
``` 
だが、これでは課題はクリアできない。上のコード例では、予め設計で決めたデータ型しか受け取ることができない。つまり、受け取るデータをdouble型と決めると、double型しか格納できないFIFOになってしまう。

そんなつまらないものを作ってどうする！
やりたいことは、double型でも、int型でも、リスト型でも Elixirで定義できるデータならば何でもFIFOに放り込みたいのだ。

さて、どうする？
あれこれと試行錯誤した結果、どうやら ERL_NIF_TERMを binaryとして受け取れば良さそうだ。使用する APIは enif_term_to_binary()。enif_term_to_binary()は、受け取った ERL_NIF_TERMを解析し、Erlang external term formatのRAWデータを生成するようだ。少々効率が悪そうなところには目を瞑ろう。

```C++:enif_term_to_binary()の例
ERL_NIF_TERM baa(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    <略>
    ErlNifBinary bin;
    if (!enif_term_to_binary(env, argv[0], &bin)) {
        return enif_make_badarg(env);
    }
    <略>
}
```
#### Wrap-up
無事、最後の課題もクリアした。

ここまでの成果と併せて、FIFOの仕様に謳った機能をインプリメントすると、NIFsのコードはこうなる。

```c++:firo_nif.cc
/***  File Header  ************************************************************/
/**
* @file fifo_nif.cc
*
* Helper templates for handling NIFs resource object
* @author   Shozo Fukuda
* @date     Create: Thu Sep 12 16:12:13 JST 2019
* System    Linux Mint<br>
*
* Produced by Shozo Fukuda
*
**/
/**************************************************************************{{{*/

/***** INCLUDE *****/
#include <erl_nif.h>
#include <deque>
#include <algorithm>
#include <mutex>

/***** CONSTANT *****/

/***** TYPE *****/
struct DequeNifBinary {
	std::mutex               m_mutex;
	std::deque<ErlNifBinary> m_deque;
};

/***** MACRO *****/
#define OK(env)  	enif_make_atom(env, "ok")
#define ERROR(env)	enif_make_atom(env, "error")

/***** EXPORT VARIABLE *****/

/***** PRIVATE VARIABLE *****/
static ErlNifResourceType* _ResType_DEQUE = NULL;

/***  Module Header  ******************************************************}}}*/
/**
* create fifo
* @par description
*   generate new fifo and resource object
*
* @return fifo handle
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
    DequeNifBinary* deque_ptr
        = new(enif_alloc_resource(_ResType_DEQUE, sizeof(DequeNifBinary))) DequeNifBinary;

    ERL_NIF_TERM term = enif_make_resource(env, deque_ptr);
    enif_release_resource(deque_ptr);

    return term;
}

/***  Module Header  ******************************************************}}}*/
/**
* peep front
* @par description
*   peep front item of fifo
*
* @return front item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_front(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
		ErlNifBinary bin = deque_ptr->m_deque.front();

        ERL_NIF_TERM term_bin;
        enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
        return enif_make_tuple2(env, OK(env), term_bin);
    }
    else {
        return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
    }
}

/***  Module Header  ******************************************************}}}*/
/**
* pop item
* @par description
*   pop item from front of fifo
*
* @return poped item or error
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

   // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
		ErlNifBinary bin = deque_ptr->m_deque.front();
		deque_ptr->m_deque.pop_front();

        ERL_NIF_TERM term_bin;
        enif_binary_to_term(env, bin.data, bin.size, &term_bin, 0);
        enif_release_binary(&bin);
        return enif_make_tuple2(env, OK(env), term_bin);
    }
    else {
        return enif_make_tuple2(env, ERROR(env), enif_make_string(env, "empty", ERL_NIF_LATIN1));
    }
}

/***  Module Header  ******************************************************}}}*/
/**
* push item
* @par description
*   push item into backend of fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_push(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 2) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    ErlNifBinary term_bin;
    if (!enif_term_to_binary(env, argv[1], &term_bin)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    deque_ptr->m_deque.push_back(term_bin);
    return OK(env);
}

/***  Module Header  ******************************************************}}}*/
/**
* clear fifo
* @par description
*   delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_clear(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    // mutual exclusion after here
    std::lock_guard<std::mutex> lock(deque_ptr->m_mutex);

    if (!deque_ptr->m_deque.empty()) {
        std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
            [](ErlNifBinary& bin){ enif_release_binary(&bin); });
        deque_ptr->m_deque.clear();
    }
    return OK(env);
}

/***  Module Header  ******************************************************}}}*/
/**
* size of fifo
* @par description
*   inquire number of contens in fifo
*
* @return size
**/
/**************************************************************************{{{*/
ERL_NIF_TERM fifo_size(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    if (argc != 1) {
        return enif_make_badarg(env);
    }

    DequeNifBinary* deque_ptr;
    if (!enif_get_resource(env, argv[0], _ResType_DEQUE, (void**)&deque_ptr)) {
        return enif_make_badarg(env);
    }

    return enif_make_uint(env, deque_ptr->m_deque.size());
}

/***  Module Header  ******************************************************}}}*/
/**
* destroy fifo
* @par description
*   delete all contents in fifo and reset fifo
**/
/**************************************************************************{{{*/
void fifo_destroy(ErlNifEnv* env, void* ptr)
{
    DequeNifBinary* deque_ptr = reinterpret_cast<DequeNifBinary*>(ptr);

    if (!deque_ptr->m_deque.empty()) {
        std::for_each(deque_ptr->m_deque.begin(), deque_ptr->m_deque.end(),
            [](ErlNifBinary& bin){ enif_release_binary(&bin); });
    }

    deque_ptr->~DequeNifBinary();  // don't delete. you can destruct it only.
}

/***  Module Header  ******************************************************}}}*/
/**
* Nifs load
* @par description
*   Nifs initializer
**/
/**************************************************************************{{{*/
int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
    _ResType_DEQUE = enif_open_resource_type(env, NULL, "mydeque", fifo_destroy, ERL_NIF_RT_CREATE, NULL);

    return 0;
}

// Let's define the array of ErlNifFunc beforehand: 
static ErlNifFunc nif_funcs[] = {
    // {erl_function_name, erl_function_arity, c_function, dirty_flags} 
    { "fifo_create",  0, fifo_create,    0 },
    { "fifo_front",   1, fifo_front,     0 },
    { "fifo_get",     1, fifo_pop,       0 },
    { "fifo_put",     2, fifo_push,      0 },
    { "fifo_clear",   1, fifo_clear,     0 },
    { "fifo_count",   1, fifo_size,      0 },
};

ERL_NIF_INIT(Elixir.FIFO, nif_funcs, load, NULL, NULL, NULL)
```
#転: Elixirインターフェイスの実装
NIFs/C++側の実装は終わった。Pythonの拡張モジュールの実装ならばココで終わりなのだが、Elixirではもう少し作業が必要だ。

さあ、Elixir側に目を視点を転じよう。

ElixirからNIFs関数をcallする為には、まずNIFsの入った動的ライブラリをロードしなきゃならない。そして、NIFs関数と同じ名前をもつSTUB関数が定義されていなければならな。STUB関数は、動的ライブラリがロードされるときに、NIFs関数でオーバーライドされるようだ。

その他諸々と併せて実装すると、こうなる。

```elixir:fifo.ex
defmodule FIFO do
  @moduledoc """
  mutable FIFO data structure & operations
  """
  defstruct handle: nil
  
  def create() do
    %FIFO{handle: fifo_create()}
  end
  
  def front(%FIFO{handle: h}) do
    fifo_front(h)
  end

  def get(%FIFO{handle: h}) do
    fifo_get(h)
  end
  
  def put(%FIFO{handle: h}, x) do
    fifo_put(h, x)
  end
  
  def clear(%FIFO{handle: h}) do
    fifo_clear(h)                                                                                                                                                                                                     
  end
  
  def count(%FIFO{handle: h}) do
    fifo_count(h)
  end

  # loading NIF library
  @on_load :load_nif
  def load_nif do
    nif_file = Application.app_dir(:fifo, "priv/fifo_nif")
    :erlang.load_nif(nif_file, 0)
  end

  # stub implementations for NIFs (fallback)
  def fifo_create(),    do: raise "NIF fifo_create/0 not implemented"
  def fifo_front(_a),   do: raise "NIF fifo_front/1 not implemented"
  def fifo_get(_a),     do: raise "NIF fifo_get/1 not implemented"
  def fifo_put(_a,_b),  do: raise "NIF fifo_put/2 not implemented"
  def fifo_clear(_a),   do: raise "NIF fifo_clear/1 not implemented"
  def fifo_count(_a),   do: raise "NIF fifo_count/1 not implemented"
end
```
#結: 結果良ければ全て良し
ふぅ、一通り実装が終わった:sweat_smile:
では試してみよう。

```erlang
> iex -S mix
Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/priv
mkdir -p /home/shozo/fifo/_build/dev/lib/fifo/obj
g++ -c -I/home/shozo/.asdf/installs/erlang/22.0.4/usr/include -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -o /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o src/fifo_nif.cc
g++ -o /home/shozo/fifo/_build/dev/lib/fifo/priv/fifo_nif.so -L/home/shozo/.asdf/installs/erlang/22.0.4/usr/lib -fPIC -shared /home/shozo/fifo/_build/dev/lib/fifo/obj/fifo_nif.o
Compiling 1 file (.ex)
Generated fifo app

Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> fifo = FIFO.create
%FIFO{handle: #Reference<0.2846310002.3727818754.139300>}
iex(2)> FIFO.front(fifo)
{:error, 'empty'}
iex(3)> FIFO.put(fifo, ["Hello", :world])
:ok
iex(4)> FIFO.put(fifo, 2.7182)
:ok
iex(5)> FIFO.count(fifo)
2
iex(6)> FIFO.get(fifo)
{:ok, ["Hello", :world]}
iex(7)> FIFO.get(fifo)
{:ok, 2.7182}
iex(8)> 

```
良さそうだな。仕様通りに動いているようだ。

#ふりかえり
関数型言語にあるまじき異端な拡張モジュール"mutableなFIFO"を実装してみた。

immutableで安全な楽園から一歩外のNIFsの世界に踏み込むと、しごく日常的で危険な世界が広がっていた。脛に多くの傷をもつ経験と勘を頼りに、安全に心掛けたNIFsの実装を行ったつもりであるが、果たしてどうだか分らない。

う～む、極論すれば入力されたデータを加工してDBに放り込むだけの、関数型言語がしっくりとfitするWEB系が羨ましいなぁ。

尚、ここに至るまでは、多くの先人たちの投稿記事を参考にさせて頂いた。感謝m(_ _)m

#参考文献
[1] [Erlang Run-Time System Application (ERTS) Reference Manual Version 10.4 / erl_nif](http://erlang.org/doc/man/erl_nif.html)
[2] ["Using C from Elixir with NIFs"](https://andrealeopardi.com/posts/using-c-from-elixir-with-nifs)　Andrea Leopardi
[3] ["Elixir Native Interoperability – Ports vs. NIFs"](https://spin.atomicobject.com/2015/03/16/elixir-native-interoperability-ports-vs-nifs)　TONY BAKER
[4] [GitHub: "Elixir Circuits - GPIO"](https://github.com/elixir-circuits/circuits_gpio)
[5] [OTP Design Principles User's Guide Version 10.4 / 7.4  Directory Structure](http://erlang.org/doc/design_principles/applications.html#directory-structure)