前回
Step 2
Step 2はデバイスドライバの実装ということですが、Step 1と同様、実際のネットワークデバイスをいじるというところまでは辿り着きません。
ただ、Loopback相当の機能を実装するので、データの入出力機能が使えるようになります。
一応Linuxの機能としてLoopbackデバイスが存在するのですが、そこに何らかの方法で接続するということではなくて、我々が実装しているソフトウェア上に、入力をただエコーするだけの関数を実装する形になります。
この章でまず1つ悩ましいのは、net_device_opsの実装でしょうか。
Rust的にトレイトとして実装したいところですが、そうするとNetDeviceをデバイスの種類ごとに異なる型として実装する必要があります。それ自体は特に問題ないですが、デバイスは1つのリストで管理されているので、トレイトオブジェクトとして管理する必要があり、かなり面倒なことになります。
他に考えられるのは、デバイス種別をヴァリアントとして持つ列挙体を実装し、デバイス種別で分岐してコールバックを呼ぶ形にするのもアリかもしれません。
もう1つの悩ましいポイントは、net_deviceに追加されたprivというフィールドです。これはデバイス種別やそのデバイス自身の固有データを持つためのフィールドですが、原作のコードではvoid*として実装されています。
Rustで全く同じようなものを実装しようと思うとBox<dyn Any>を使う方法などが考えられそうです。
いずれも悩ましいポイントですが、後者はおそらくどんなデータ構造でもアリというわけではなく、サポートしているデバイスごとに決まりきったデータを格納するだけだと考えられるので、デバイス種別ごとに構造体ヴァリアントを持つようなenumを持たせるというのがいいかもしれません。
そうすると1つ目のnet_device_opsの実装もヴァリアントごとに分岐させるような感じで実装できて嬉しいです。ただ、コールバックの呼び出しごとに分岐が発生するので、パフォーマンス上嬉しくないかもしれません。まあこれは実測して確認しつつ、問題があるならリファクタリングするしかないでしょう。
コールバックはpriv相当のデータのメソッドとして実装しても良さそうですが、そうするとpriv側から所有者であるNetDeviceのフィールドにアクセスできません。
原作でのコールバックはnet_deviceへのポインタを受取りますが、同じようにNetDeviceへの可変参照を渡そうとすると、渡す可変参照とprivのメソッドのレシーバとで可変参照が2回出現してしまいます。
なので、コールバックは原作と同じように関数ポインタで保持することにします。そして、privからゲッタを通じて関数ポインタを取得します。こうすると、コールバックがNetDeviceの可変参照を受け取ることで、NetDeviceのフィールドにアクセスできますし、privのヴァリアントの構造体フィールドにもアクセスできます。
原作では、open, closeはNULLでもよいが、outputは必須ということになっています。
C言語では関数ポインタをNULLにすることができるので、outputに対するNULLチェックが必要ですが、RustではOptionでラップしない限りNULLにはできないので、そのようなNULLチェックは不要です。
open, closeはOptionでラップして、使用するときにはif let Some(...) = ...で引き剥がすようにします。
net_inputはNetDeviceのメソッドとして実装するか、独立した関数として実装するかで悩ましいところです。
net_device_outputと命名が異なるのが気になるところですが、とりあえずNetDeviceのメソッドとして実装します。不都合が出たら関数として分離します。
実装は以下のような感じになりました。
GitHub - tayu0110/ty-microps at step2 · GitHub
おおよそ上述の通りの内容になっています。
ログ用のマクロについてですが、logクレートのデフォルトのフォーマットではどのモジュールから発生したログなのかは表示されますが、どのファイルのどの関数から出てきたログなのかは表示されません。
これがちょっと不便になりつつあったので、logクレートのマクロをラップして、ファイルと行を表示させるようにしました。どの関数から発生したものかを表示させるのは難しそうだったので、それはやめました。まあファイルと行数がわかればデバッグには十分でしょう。
Rustにも__func__相当のマクロが欲しいですが、将来的に実装されることはあるんでしょうか。
マクロの定義箇所はなんかごちゃごちゃしていますが、macro_rules!のなかでmacro_rules!を使ってボイラープレートを削減しようとしたところ、macro_rules!の中で#[macro_export]をするとエラーが発生してしまったのでこうなっています。
なんで今の状態ならエラーが出ないのかはよくわからないのですが、とりあえずこれで動いているのでヨシとします。
warnマクロはそのままエクスポートしようとしたところ、warnという組み込み属性マクロがあるのでダメだと言われたのでこうなっています。よくわかりませんが、別名でマクロ定義してasで改名してエクスポートするのはセーフらしいです。
次回はStep 3をやっていきます。