世界を動かす技術を、日本語で。

Zigの破壊的変更 – 初期のWritergate

概要

  • Zigの std.ioリーダー/ライターAPI が大幅に刷新
  • 非ジェネリック化バッファ位置の変更 による最適化
  • フォーマット印刷やAPI呼び出しに 破壊的変更
  • 旧APIは非推奨、新APIへの適応方法も案内
  • 今後も段階的に関連変更を 順次マージ予定

Zig 標準I/Oリーダー・ライターAPI大刷新の概要

  • 既存の std.ioリーダー/ライター全廃止、新しい std.io.Reader/Writer へ統一
  • 非ジェネリック型 採用、バッファを インターフェース側 に配置
  • 最適化の透明性 確保、バッファ操作はホットパスで直接処理
  • vtable呼び出し はバッファが一杯の時のみ発生
  • 破壊的変更 となるため、移行には注意が必要

主なAPI変更点と移行ガイド

  • フォーマット関連のAPIが 大幅変更、既存コードは -freference-trace で修正箇所特定推奨
  • 主なリネーム例
    • std.fs.File.reader → std.fs.File.deprecatedReader
    • std.fs.File.writer → std.fs.File.deprecatedWriter
    • std.fmt.format → std.fmt.deprecatedFormat
    • std.fmt.fmtSliceEscapeLower/Upper → std.ascii.hexEscape
    • std.fmt.fmtSliceHexLower/Upper → {x}/{X}
    • std.fmt.fmtIntSizeDec/Bin → {B}/{Bi}
    • std.fmt.fmtDuration/DurationSigned → {D}
  • フォーマットメソッドのシグネチャ変更
    • *anytype → std.io.Writer
    • FormatOptions削除
    • std.fmt.Formatterコンテキスト型を明示的に
  • 旧APIは非推奨 (削除はまだ)
    • GenericReader/Writer, AnyReader/Writer → Reader/Writer
  • adaptToNewApi() による旧APIから新APIへの アダプタ利用例 あり

新APIの特徴と利点

  • 便利な新API を多数追加、パフォーマンス向上、非ジェネリック化
  • デリミタまでの読み込み が簡単に
  • 他言語にはない独自概念
    • discard (読み込み時の無視):不要データの効率的スキップ
    • splat (書き込み時のmemset的操作):O(M*N)→O(M)の効率化
    • ファイル送信 :OSがサポートすればfd-to-fdコピー可能
  • バッファ管理 :ユーザー側でバッファ提供、必要サイズは実装が決定

std.fs.File.Reader/Writerの新機能

  • Reader :ファイルハンドルに関する情報の メモ化
    • statによるサイズやエラー
    • 現在のシーク位置
    • シーク時のエラー
    • 位置指定/ストリーミング読み込み の判定
    • fd-to-fdシステムコール 利用可否
  • Writer :書き込み時にも同様のメモ化

今回の変更に含まれない内容

  • I/O as an InterfaceAsync/Await復活 は含まず
  • 以下のリワークや削除も未着手
    • tls, http, json, zon, zstd, flate, zip, パッケージ取得
    • fifo.LinearFifo削除
    • 非推奨APIの完全削除
  • これらは 別ブランチで順次PR予定

今後のマージ計画とTODO

  • テスト失敗の修正
  • std libのテスト修正
  • std.io.WriterのTODO解決
  • std.fs.File.Writerの実装完了 (特に位置指定モード対応)
  • count: usizeとcount: u64の扱い検討
  • manifestのキャッシュ処理の互換性確認

まとめと今後の展望

  • 大規模なAPI刷新 により、ZigのI/Oはより 最適化・便利化
  • 既存コードの修正は必須、移行ガイドを参考に対応推奨
  • 今後も 段階的な変更 を予定、開発者は継続的なキャッチアップが重要

Hackerたちの意見

タイトルを見たとき、これが実装されるのかと思ったよ。 https://github.com/ziglang/zig/issues/5973

だから、自動化ツールがあって、言語や標準ライブラリのセマンティックな変更を行えるのはいいことだよね。Goにはgo fixがあって、1.0以前の頃にしか使われてなかったと思うけど、これは失われることはないんだ。この手のツールはリンターやリファクタリングツールの基盤として使えるからね。Zigにもそんな解決策はあるのかな?

zig fmtには、新しいZigバージョンにソースコードをアップグレードするための自動修正がいくつかあるけど、私の知る限り、言語の変更にしか対応してなくて、標準ライブラリの変更には対応してないんだよね。

これに関しては、Zigのロードマップ2026のストリームで他のことと一緒に発表されたんだ。VOD: https://youtu.be/x3hOiOcbgeA

趣味でZigを開発している身としては、こんな基本的な部分での破壊的変更を見るのは残念だけど、1.0前の言語で開発するってことはそういうことだって理解してるよ。Zigチームが過去よりも移行を手助けすることにもっと投資してくれることを願ってる。過去の破壊的変更の経験から言うと、下流の開発者はどう修正すればいいかの明確なガイダンスなしに放置されちゃったんだ。Zig 0.12.0(ちょうど1年前にリリースされた)では、ビルドシステムにたくさんの破壊的変更があったけど、リリースノートには全く説明がなかったんだ。私が言いたいことを理解するには、Zig 0.11.0プロジェクトで行った変更[0]を見て、それからリリースノート[1]でその変更についてのガイダンスを探してみて。ほとんどの破壊的変更は言及すらされてなくて、0.11.0から0.12.0への移行方法なんて全然説明されてないよ。 >「君たちの中には死ぬ者もいるだろうが、それは私が受け入れる犠牲だ。」 >-ロード・ファークアッド [0] https://github.com/mtlynch/zenith/pull/90/files#diff-f87bb35... [1] https://ziglang.org/download/0.12.0/release-notes.html

hareの例を興味深く見てるよ[0]。それに、シャrekが大好きなのは皮肉じゃないよ。昔、NYCの小さなアパートでShrek Retoldの視聴パーティーを開いたことがあるんだ :D [0] https://harelang.org/blog/2025-06-11-hare-update/ [1] https://www.youtube.com/watch?v=pM70TROZQsI

だから、bunみたいなプロダクションプロジェクトがZigを使うって聞くと驚いちゃうんだよね。言語自体が悪い選択だとは思わないけど(デザインの決定にはいくつか異議があるけど)、こんな風に破壊的な変更があると、大きなコードベースで頻繁に1.0未満の言語を使うのは面倒だなって思う。

言語やライブラリの作者が、LLMが最初の修正を試みるために必要な変更を詳しく説明した良いプロンプトを含めてくれたらいいなと思う。決定論的なコードモッドでできればもっといいけど、プロンプトの方が書きやすいよね。

実際のAPIについて、破壊的変更の側面ではなくて、ちょっと話を始めたいんだけど、Reader.stream(writer, limit)Reader.streamRemaining(writer)の関数は、プッシュベースのデータ変換パイプライン(GREPや圧縮/暗号化みたいな)を構築するのに特にエレガントだと思う。状態機械のためにWriterインターフェースを実装して、出力を別のWriterに流し込むだけで、バイトがどう来てどう出て行くか(ソケットでも共有メモリでもファイルでも)を気にしなくて済むんだ。バッファサイズを設定するだけで、ゼロに設定することもできるみたいだし!Writer.sendFile()もいいね。こういうプリミティブを「ジェネリックインターフェース」で提供しているストリーム抽象は他に知らないよ。普通はストリームを「FileStream」にダウンキャストして、ファイルディスクリプタを直接扱わなきゃいけないから。

インターフェースのsendfileについてだけど、これは重要なんだ。なぜなら、ストリームを「FileStream」にダウンキャストするのは、パイプラインがA -> Bのときはうまくいくけど、途中にアイテムを挿入すると(A -> B -> C)崩れちゃうから。今、File -> tar -> HTTP(Transfer-Encoding: chunked) -> Socketのデモを持ってるけど、直接fd-to-fdコピーがチェーン全体を通過するんだ!

こんな大きな変化があると、Zigが今後他のデザイン選択を見直して改善してくれるかもしれないって期待しちゃうね。

見直してほしい選択肢について詳しく教えてもらえる?

実際のAPIについて理解できないことがあるんだけど、私はziglerライブラリをメンテしてるんだ。昔のasyncの「色付きだけど実際はそうじゃない」関数が便利だったのは、内部のサスペンドポイントを暗黙的に許容してたからなんだよね(詳細: https://www.youtube.com/watch?v=lDfjdGva3NE&t=1819s)。IOをパラメータとして渡すことでそれができるのか分からないんだけど、ユーザーは自分の関数をサスペンドポイント付きで作れるのかな?それと、関数のフレームから抜け出して実行者に制御を返して、後で再開できるの?

こんにちは、アイザック。バーチャルだけど会えて嬉しいよ。ご存知の通り、その言語の機能(「スタックレスコルーチン」、「ジェネレーター」、「関数ロジックをステートマシンに書き換える」)は後退しちゃったんだ。最初はこの新しいIOインターフェースにはその機能がないけど、フォローアップの課題として、IO実装内で使えるように、より低レベルな形で再導入したいと思ってる。制限された関数ポインタと組み合わせることで、サスペンドできる関数がランタイムで知られている関数ポインタの境界を越えて通過できるようになるんだ。これは以前はすごくもたついていて、全体のデザインを損なうほどだったからね。つまり、同じIOインターフェースの使用コードが再利用できるようになって、サスペンドポイントを使う実装でも、呼び出し規約の自動書き換えがインターフェースを通じて使用コードに伝播できるようになるってこと。追跡すべき課題はこれだよ: https://github.com/ziglang/zig/issues/23446 それに、私はこの問題の解決策として以前のサスペンド/リジュームキーワードとセマンティクスにまだ興味があるって付け加えておくね。

古いリーダー/ライターパターンに関して一つ問題があるのは、構造体にそれらを格納するのが簡単じゃないことなんだ。リーダーとライターは、read()やwrite()関数を実装する'anytype'として関数に渡されるんだけど、構造体のinit()関数では、リーダー/ライターを受け取って後で使うために保存したいんだ。でも、どの型のフィールドに格納すればいいのか分からなくて、ほぼ不可能なんだよね。新しい変更で、構造体にリーダー/ライターを格納するのが楽になるのかな?

そう、それが「非ジェネリック」の意味だよ。

この動画を見てみることをおすすめするよ。 https://youtu.be/x3hOiOcbgeA

私はZigとElixirを接続するライブラリをメンテしてるけど、破壊的な変更は大した問題じゃなかったよ。ライブラリの一部としてZigパーサーを維持してるんだけど(langrefの公式パーサーは最新じゃないから)、私のパーサーでZigのコードベース全体をスキャンして、何かに引っかからないか確認してるんだ。それに、Elixirに埋め込まれたテンプレートコードがあるから、Zig fmtを使って追いつくこともできない。最近の2回のセマンティックバージョンのマイナーアップデートで、言語に追いつくためにやらなきゃいけなかった変更は、合計で多分1時間くらい?主観的には、私に影響を与える破壊的な変更は、初期の頃よりも少なくて対処しやすくなった気がする。ライブラリの更新が遅れたのは、前回の更新から先延ばしにしていたことを片付けていたからなんだ(それに、私は「テクノロジーを辞めた」から$DAYJOBではライブラリを使ってないけど、自分のためにvibecodeソリューションを作ってる)。要するに、Zigの変更について文句を言ってる人たちは、実際には日常的に使ってるわけじゃなくて、ただ!!破壊的な変更!!の発表を見て騒いでるだけの人たちだと思う。でも、新しい人たちには同情するよ。インターネットの記憶は、動かないデモコードがたくさんある古いバージョンに固定されてるからね。もちろん、長い目で見ればLLMにも影響があるだろうし。Zigがグローバルな知識や人気のLLMサイクルのカットオフに引っかからないことを願ってる。そうならないと思うけど。

新参者にとっての問題は、僕がZigを使おうとしたときに直面したことだね。特にビルドシステムについて、Zigのビルドシステムが他のツールよりもどれだけ素晴らしいかをよく聞いていたんだけど、C/C++のビルドシステムに不慣れな僕には、設定がすごく難しかった。対照的に、その後CMakeを学ぼうとしたんだけど、CMakeの言語自体に不満があったにもかかわらず、やりたいことは比較的簡単にできた。ドキュメントや後方互換性、LLMのおかげで、すべての設定が楽だったよ。デスクトップ/WASM向けにデバッグ/リリースビルドするハイブリッドのC++/Rustプロジェクトがあるんだけど、Zigのビルドシステムが安定すればもっと良くなると思う。ただ、最近の経験からすると、破壊的な変更は厳しいね。

RubyとElixirの間を行き来しているZigに興味がある者として、もし良いリンティングツールがあって通知したり自動修正してくれたら、これが問題になるとは思わないな。