Zig言語用のClone-on-Writeなヘルパー型作った

## あらまし

`Zig`はヒープのメモリ確保/解放を自前でやることが基本である。
文字列(`[]const u8`)を扱い出すと、誰が所有者なのか、静的確保されたリテラルなのか、動的確保されたものなのかを常に意識しておかないと、いともたやすくメモリエラーを起こす。

メモリエラーで落ちてくれればまだマシで、他の値が壊れたり、しばらく回してると気づかないうちに`Allocator`がぶっ壊れた結果`SegFault`に陥ったりすると原因究明がマジ辛い。

だもんで、防御的に文字列を複製するわけだけど、今度は所有者が曖昧になってメモリリークが頻発して悩ましい。

## ってことで

文字列の参照を共有し、変更したり付け替えで参照を切り離すヘルパー型作ったった。

https://github.com/ritalin/zig-cow

## インストール

`README`に記載したように、最初に以下のコマンドを実行して、`build.zig.zon`に追加する。

```
zig fetch --save git+https://github.com/ritalin/zig-cow
```

次いで、`build.zig`から依存を追加する

```zig
const dep = b.dependency("zig_cow", .{});
exe.root_module.addImport("cow", dep.module("cow"));
```

あとは、ソースにインクルードすることで使えるようになる

## 使い方

### リテラルを共有する

内部でヒープアロケーションしているため、`initAsCopy`関数で初期化する。

```zig
const cow = @import('cow');

// Cスタイル文字列の場合は代わりにcow.CStringCowを使う
var s = cow.StringCow.initAsCopy("Hello World");
```

一方、動的に生成した場合は、`initAsMove`を使う[^1]。
[^1]: 別にinitAsCopyでも構わないが、動的に生成した文字列は責任を持って自分で解放すること

### 共有

文字列を共有する場合は、`share`メソッドを呼んで渡す。

```zig
var s2 = s.share();
```

`Struct`のフィールドの初期化や別スレッドに渡す場合がほとんどだと思う。

```zig
const Foo = struct {
  s: cow.StringCow,
};

const foo: Foo = .{
    .s = s.share(),
};
```

### 共有解除

参照の解除は、`deinit`を呼ぶ。
どこからも参照されなくなったら自動的に消滅する。

```zig
s2.deinit();
```

`Struct`の場合は、`deinit`メソッドを用意して、その中で呼べば良い。

```zig
const Foo = struct {
    pub fn deinit(self: *Foo) void {
        self.s.deinit();
    }
}
```

### 参照の付け替え

引数で渡されてきた対象に付け替える場合は、`assignAsCopy`を呼ぶ。
このメソッドは、内部的に`share`を呼んで共有している。
また、もともと保持していたものは共有を解除する。

```zig
fn hoge(self: *Self, ss: cow.StringCow) void {
    self.foo.s.assign(ss);
}
```

ローカルスコープで存在している対象に付け替える場合は、`assignAsMove`を使う[^2]。
[^2]: assignAsCopyでも構わないが、動的に生成した文字列は責任を持って自分で解放すること

```zig
fn hoge(self: *Self, allocator: std.mem.Allocator) !void {
    const hw = try allocator.dupe(u8, "Hello World");

    self.foo.s.assignAsMove(cow.StringCow.initAsMove(hw));
}
```

### スライスの読み取り

`r`メソッドを呼ぶことで、`StringCow.Readable`インターフェースを返す。
このインターフェースの`buf`フィールド (`[]const u8`)でやりとりする。

```zig
std.debug.print("{s}\n", .{foo.s.r().buf});
```

### スライスの内容の書き換え

`w`メソッドを呼ぶことで`StringCow.Writable`インターフェースを返す。
このインターフェースの`buf`フィールド (`[]u8`)に対して書き換えを行う[^3]
[^3]: 既存のスライスの内容を変更することしかできず、長さを変えたりとかはできないことに注意。

### 別のスライスへの変更

`mut`メソッドにスライスを返すコールバックを渡す。

コールバックの型は

```zig
// Cスタイルのスライスなら[:0]const u8を返すコールバックを用意する
fn (allocator: std.mem.Allocator, old: StringCow.Readable) []const u8;
```

### SliceとSentinel Sliceの相互変換

そもそも型が違うため、`StringCow`と`CStringCow`の相互変換はできません。
`Zig`としては、以下のような代入が可能だけども、

```zig
var s: []const u8 = undefined;
var s1 = try allocator.dupeZ(u8, "Hello World");
s = s1;
```

うっかりこれやって、1 Byteのメモリリーク（終端バイトが落とされる）探すのに数時間溶けたので、制約として防げて逆にハッピーかも

## FAQ

1\. `w`メソッドってビミョーじゃね？

当初は、`r`と`w`だけあれば問題ないっしょって感じで仕様決めてたけど、`Struct`フィールドへの再割り当てが必要なことに気がついた。
結果、大抵のことは`init`して`assign`で事足りるので、ぶっちゃけ不要。

２\. `mut`に渡すコールバックの定義がダルいんですけど？

大抵のことは`init`して`assign`で事足りるので、ぶっちゃけ不要。
メソッド名が思い浮かばずに数時間溶けたので、思い入れで残してるだけ。

3\. なんで`init`って関数名じゃなく、`initAsCopy`や`initAsMove`なの？

* コピーセマンティクスにすべきところをムーブセマンティクスした場合の障害はダングリングポインタの二重破棄。
* ムーブセマンティクスにすべきところをコピーセマンティクスした場合の障害はメモリリーク。

として顕在する。
その際、出所を検索しやすいよう意図的にこのような命名をした。

### おわりに

このヘルパーを作る上で、外から保持しているスライスに触られると困るのでなんとか隠蔽したかった。
その際に`Opaque`が使えるかなってことで試してみた。

`Opaque`に関数実装を持たせることはできないため、ファイルスコープの関数名を定数で結びつけることで、制約を組み込むことに成功した。

```zig
const Internal = opaque {
    const init = initInstance;
    const clone = cloneInstance;
    const write = asWritable;
    const read = asReadable;
    const mut = mutateBuffer;
    const share = shareBuffer;
    const unshare = unshareBuffer;
    const counter = currentRefCounter;
};
```

今まで`Opaque`をどう使えば良いのか見出せてなかったが、一つのユースケースとして使っていけそうな気がした（気がしただけ）。

あと実際に組み込んでみて、つかえねーってなったら塩漬けにする。

### 追記

参照を共有するという仕様上`ArenaAllocator`と絶望的に相性が悪いです。
`ArenaAllocator`の`deinit`で問答無用に解放されるため、別スレッド先では共有している場合、SegFaultな形で露見すると思われます。