💡

Zigのunionを使って他の言語のinterface相当のことを行う方法

2023/01/30に公開

読み書きする対象としてファイルとメモリ中のバッファとを統一的に扱いたい

Zigで書く練習としてtftp_clientを書いています。
これをコマンドとして使うときにはファイルに対して読み書きを行いますが、ライブラリとして使うときには[]u8 のバッファに対して読み書きできるほうが便利です。これを統一的に扱う方法はあるでしょうか。
Golangだったらio.Reader, io.Writerのinterfaceを使う場面です。

std.io.StreamSource

ちょうどぴったりのものを標準ライブラリの中から見つけることができました。
std.io.StreamSource
file descriptor の代わりにこれを引数にとるようにすれば、呼び出す方でファイルにするか[]u8 のバッファにするか選ぶことができます。
これを使って以下のような関数を書いてみました。

fn copy(dst: *io.StreamSource, src: *io.StreamSource) !usize {

このcopyを使って、buf -> file, file -> buf のコピーをテストしました。

std.io.StreamSource の実装を見てみる

StreamSourceの実態はフィールドを一つだけ持つunionです。

https://github.com/ziglang/zig/blob/0.10.1/lib/std/io/stream_source.zig#L5-L21

以下のようにメソッドが定義されていて、buffer, const_buffer, file のそれぞれごとのメソッドを呼び出すようにswitch文を使って委譲されています。

https://github.com/ziglang/zig/blob/0.10.1/lib/std/io/stream_source.zig#L40-L46

要するに、unionを定義して、委譲するメソッドを書けば他の言語のinterface相当を実現することができます。
switch文の分岐が全て同じ場合には inline elseを使って記述を簡略化することができます。以下のページが参考になります。

https://zig.news/kristoff/easy-interfaces-with-zig-0100-2hc5

Discussion