2 ポイント 投稿者 GN⁺ 2025-08-25 | 1件のコメント | WhatsAppで共有
  • Zig 0.15で 新しいIOインターフェース(std.Io.Reader, std.Io.Writer) が導入された
  • 従来のIO方式における複雑さやパフォーマンス問題の改善が目的だが、実際の使い方に混乱 が生じている
  • tls.Clientbuffer の使用に関連して、統一されていないパラメータ受け渡し方式が混乱をさらに大きくしている
  • 基本的な使用例を実装する際にも、複数の バッファサイズ、オプションフィールド 指定など複雑な要件が存在する
  • 公式ドキュメント、コード例、便利関数の不足により、入門者にとって直感的ではない

Zig 0.15で導入された新しいIOインターフェースと背景

  • Zig 0.15で std.Io.Readerstd.Io.Writer という新しいIO型が導入された
  • 以前のIOインターフェースは、性能問題や型の混在、そして anytype の濫用などによって複雑さを生んでいた
  • 新しいIO構造では、インターフェース間の明確な型の区別と性能改善が主な目標となっている

tls.ClientとIOインターフェース使用時の実際の問題点

  • 既存のsmtpライブラリを更新する過程で、tls.Client.init 関数の使い方で混乱が発生
  • ドキュメント上では init 関数は ReaderとWriterのポインタ、オプションセット を引数に取ると記載されている
  • Zigの net.Stream はそれぞれ reader()writer() メソッドで Stream.Reader/Writer を返す
    • しかし Stream.Reader/Writer と std.Io.Reader/Writer は正確に同じ型ではないため変換が必要
    • Reader は interface() メソッド呼び出し、Writer は &interface フィールドを使う必要があり、一貫性に欠ける

バッファとオプションフィールド設定の問題

  • stream.writer、stream.reader はそれぞれバッファを引数に取る
    • Buffer が新しいIOインターフェースで必須の要素として強調されている
  • tls.Client.init の呼び出し時には ca_bundle、host、write_buffer、read_buffer など 4つのオプションフィールド が必須
    • オプション引数として渡す値と、引数として直接渡す値の分離ルールが不明瞭に感じられる
var tls_client = try std.crypto.tls.Client.init(
  reader.interface(),
  &writer.interface,
  .{
    .ca = .{.bundle = bundle},
    .host = .{ .explicit = "www.openmymind.net"; } ,
    .read_buffer = &read_buf2,
    .write_buffer = &write_buf2,
  },
)
  • 実際には buffer ポインタが適切に渡されていないと、プログラムが正しく動作しなかったり、hang や crash などさまざまな問題が発生する

Reader使用時の直感性の問題

  • tls.Client の reader フィールド自体は「復号されたストリーム」であるにもかかわらず、実際の std.Io.Reader には一般的な read メソッドが存在しない
  • 代わりに peek、takeByteSigned、readSliceShort など、あまり直感的でないメソッドだけが提供される
  • その中で実用に近いAPIは、stream メソッド を通じてバッファへデータを読み込む方式である
var buf: [1024]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const n = try tls_client.reader.stream(&w, .limited(buf.len));

全体コード例と実運用上の問題

  • 全体として動作する最小構成の例を作ろうとしても、オプション、バッファサイズ、型変換など気を配る点が多い
  • テスト、ドキュメント、サンプル不足により、学習難易度と参入障壁が高い
  • Zig言語内での一貫性、あるいは underlying design への理解が不足している場合、違和感を覚える点が多い
  • 標準ライブラリ内でもこの方式があまり使われておらず、実践的な参考資料が不足している

経験と結論

  • std.fmt.printInt などの命名変更、API design の変化などにより、migration の過程自体が容易ではない
  • reader.interface()、&writer.interface 方式やオプション受け渡し方式、複数のバッファが必要な点など、繰り返し多くの難しさを経験
  • TLS などのネットワーク/セキュリティプロトコルに慣れていない立場では、要件把握がさらに難しく感じられる
  • 総合的に見て、従来と比べて明確さ、ドキュメント整備、利便性改善の面で まだ不十分な部分 が多く存在する

1件のコメント

 
GN⁺ 2025-08-25
Hacker Newsの意見
  • 作者本人です。ついにちゃんと動くようになりました。暗号化writerとstream writerの両方でflush処理が必要で、同時に読み取り側にも問題がありました。ストリーミング自体は動くのですが、Writer.FixedsendFile を実装していないため、最初の読み取りでは常に0を返します。最初の呼び出しの後、内部的にストリーミングモードから読み取りモードへ切り替わって、突然すべてが動き始めます(関連コード: Zig File.zig #L1318)。今はwebsocketライブラリで圧縮機能を再び有効にしようとしています

    • 「Flushを忘れるな」というYouTubeミームを思い出しました(YouTube動画

    • 最小驚愕の原則(principle of least surprise)はいったいどこへ行ったのかと思います

    • 以前のインターフェースから今の状況に移ったのは、ある意味ですごいです。驚きが大きい

  • Zig PM(プロダクトマネージャー)ではありませんが、OPが経験した問題に対する明らかな第一の解決策は、より良いドキュメントと、より多くの使用例を作ることです(多すぎても構いません)。こうした作業は、ユーザーに求めすぎていないかを振り返る良い機会にもなるはずです。目標が絶対的な性能、または性能低下を招く抽象化の導入回避だったのなら、その目標は達成できているように見えますが、DX(開発者体験)ははるか彼方へ飛んでいった感じです

    • Zigコミュニティの文化をよく分かっていないようです。ドキュメント不足に不満を言うと、誰でも「stdlibのコードを自分で読め」というコメントが殺到する覚悟をしておく必要があります。ほとんどのAPIはこの投稿のように使いづらく、HTTPやファイルシステムのような基本的な作業でさえ、慣れていないと本当に大変です。だから本当に実力のある人だけが生き残ります

    • ドキュメント作成にはコストも時間もかかります。その時間でZigの別の部分を改善することもできます。開発中のコードなら、完全に落ち着くまでドキュメントを後回しにするのも合理的な選択です。もちろんドキュメント化は望ましいですが、新機能、重大なバグ修正、ドキュメント作業のどれを優先するか選ばなければならない場面では、常に全部を取れるわけではありません

    • zigは何をしてはいけないかを指示することに重点を置きすぎている気がします。いろいろなやり方や使用例を集めて、うまく整理して伝える方向に進んでほしいです。このインターフェースのドキュメント欠如はその代表例です

    • 良いドキュメントやサンプルを書くには多大な労力が必要です。今zigで起きている変化の大きさを見ると、きちんと定着する前にドキュメントを書いてもすぐ無駄になります

    • 私はZigの開発者ではありませんが、Zigのドキュメントがとても簡素な理由の一つは、言語がまだ若く、進化の途中だからだと思います。今書いたドキュメントが将来すぐ間違いになると分かっていながら、時間とエネルギーを注ぐのが難しいというのは理解できます

  • Zigという言語そのものは本当に悪くないのですが、標準ライブラリはまだかなり未完成で、変化が激しく、不足も多く、一部は抽象的すぎる一方で、逆に低レベルすぎます。今は標準ライブラリの代わりにOS APIを直接使う方がよいと思います。ベータテスターになる覚悟がないなら、標準ライブラリは避けることを勧めます

    • 実際、私もzigを使うときはたいていOS API中心です。cImports がよくできているので、zigの定義を作るのが面倒なときでも気軽に使えます

    • 私から見ると、Zigは一度にあまりにも多くのことをやろうとしていて、自分が考える最低限の品質ラインにすら到達できていません。ユーザーに急激な変化と実験を受け入れさせる姿勢からは、十分な人数が投資して「1.0前なら壊れていても仕方ない、いつか良くなる」という幻想を信じるしかない状況に見えます(結論として、その日は永遠に来ない気がします)。自分の実験の負担を他人に背負わせるのは望ましくないと思います。不安定だと事前に告知していたとしても、依存しないでくれと言っていたとしても、実際にrug pull(突然すべてが変わる)を食らう側にとっては問題です。zigが何なのかよく分かりません。Matkladはmachine level languageだと言い(関連インタビュー: lobste.rs - Matkladインタビュー)、公式ページではrobust、optimal、reusableなgeneral-purpose言語だとしています。この二つは矛盾しています。そして手動メモリ管理が不要な問題も多いので、zigは決して汎用言語ではありません。結局、この混乱のすべてがzigの不安定さと肥大化した標準ライブラリに表れています。シンプルさと汎用性を掲げながら、これほど大きなライブラリを抱えるのは矛盾です。Asyncも、すべてのプラットフォームで普遍的かつ効率的に実装できる機能ではないのに、万能の解決策であるかのように約束していました。以前は関数彩色問題を解決したと宣伝していましたが、その試みはすでに捨てられています。ちゃんとやり遂げられると再び信じろという理屈は奇妙です。実際には、すべてのプラットフォームでコンパイラを実装するのに必要なのは基本的なアセンブリ命令だけで、luajitはパーサーを純粋なアセンブリで実装していて、どこでもちゃんと動きます。私は普段ほとんどluaでプログラミングしていますが、インタプリタでバグにほとんど遭遇したことがありません。zigがluajitよりうまく解決してくれる問題も思い浮かびません。仮にzigでしか解決できないものがあっても、それをluaコードにembedしてFFIで連携すれば済みます。ほとんどのコードは、そこまで低レベルの最適化を本当に必要としていません。zigを導入すると、むしろ面倒になるだけです。最近のzigへの誇大な期待は、AI並みに現実との乖離に達しています。zigを信じるには、今は存在しない能力をいつか手に入れるという空虚な希望を信じる必要があります。実際には実行計画もなく、ただ「もう少し待って」と言っているようなものです

  • ライブラリやインターフェースが自分の型のバッファ割り当てを要求してくるのが理解できません。自分でパースするならそもそもライブラリは要りませんし、使うとしても交換可能性を壊しかねません。goの独特なインターフェースは、一部のインターフェースがwriterインターフェースを拡張していたり(hijackerインターフェース参照)、requestオブジェクトが複数のミドルウェアでさまざまに再利用されたりすることに由来します。要するにrequestは拡張される必要がありませんが、responseはwebsocketやtcpラッパーなど多様な形に変わりうるということです

    • ライブラリが外部バッファの割り当てを要求すること自体は、そこまで変には感じません。柔軟性を与える代わりに、より多くの手作業が必要になるだけです。たとえば、すでに用意してあるバッファプールを再利用したいことがあるでしょう。型が内部で独自に割り当てるなら、それはできません。あるいは、あらかじめすべてのリソースを割り当てておかなければならない環境では、後からの割り当ては不可能です。欠点は、利用者全体のうちこうした柔軟性を本当に必要とするのは10%だけで、残り90%は単にバッファを割り当てて渡すだけなのに、全員がより複雑なことをしなければならなくなる点です。最善なのは、高い柔軟性を提供しつつ、単純なケースは簡単に扱えることです。たとえば、長さ0のバッファ(またはZigのnull)を渡したら型が自分で割り当てるようにし、さらにバッファなしで簡単に生成できるコンストラクタも用意すればよいでしょう。もちろん、そういうものは完全にドキュメント泣かせですが

    • これは言語ごとに選ばれる慣習の違いに近いです(ラジアンと度の違いのようなものです)。どんなIOでも自由に変換できます。ある側ではmockと呼び、別の言語ではunsafeFooと呼ぶかもしれません。Andrew Kelleyはライブ配信で、Haskellコミュニティが30年間議論してきたパターンを独自に再発見しました。だから未来はZigです。彼が先に悟ったのです

    • 外部バッファの意味は、関数がバッファ割り当てを省略できるということです

  • zigのサイドプロジェクトを0.15.xに上げるつもりはありません。Andrewがなぜリリースを選んだのか、そして新しいIOをアーリーアダプターに渡したことは尊重します。でもreaders/writersの大規模変更の直後で、まだ数日しか経っていません。標準ライブラリの作業者にとっては良いことですが、私のように趣味でzigを使う人間にとっては、0.16.0で安定してからまで待つのが賢明だと感じます

    • 言語名がZigなら、ときどきはZagもしないといけないのでは、という冗談を思い出しました

    • ZigコアメンバーのLoris Croも最近のインタビューで、IO変更の余波が収まるまで自分のプロジェクトでzigの更新を遅らせると言っていました。ただ、その後の見通しは前向きです。AndrewとLorisの両方が、これが最後の主要変更になるだろうと見ているので、1.0はそう遠くないのではと期待しています。再導入されるstack-less coroutineの影響だけが、今のところ最大の変数です

  • 新しいIOインターフェースについての投稿を見たあと、zigを避けることにしました。ありがたいことに、自分の直感は正しかったと思います。理由は違っていても、結果としてC++11以前の冗長さに近い複雑さを感じます。新しい言語が置き換えを目指しながら、結局は既存言語と同じくらい複雑になっていくパターンが、また繰り返されています

    • それらの投稿の一つは私のものです。こういう投稿を見てzigに挑戦するのを怖がる必要はないと思います。zigチームは、より良い解決策が見つかれば大胆に変える姿勢を持っています。zigを将来の飯の種として考えるなら、こうした変化は合わないかもしれませんが、個人や小規模チームにとっては、すでに目的が明確で、ツール類も良い、魅力的な言語です
  • Stream.Readerstd.Io.Reader に変換するには interface() メソッドを呼び、Stream.Writer から std.io.Writer を得るには &interface フィールドのアドレスが必要だというOPの指摘は、一貫していないのでGoコミュニティなら却下していたと思います。Goは小さな変更であっても、極めて深い分析の末に決める傾向があります。私のお気に入りのGo issueの例: Go github issue #45624。4年議論してから結論を出す、という感じです。遅いかもしれませんが、一貫性、設計上の検討、実際のコードでの使われ方まで丁寧に見ています。遅いですが、それだけ必要な速度なのだと思います。そうして下された決定は、結果として非常に高品質です

    • Rustも同じです。nightly rustにしかなく、stableにはない便利な機能(たとえばgenerator)がたくさんあります。もどかしいですが、stableに入る機能は非常に深く検証されています。私はせっかちですが、Rustチームのやり方は望ましいと思います

    • Goも1.0前はそこまで遅くありませんでした。より根本的ではないにせよ大きな変更が頻繁にあり(セミコロン削除、エラー型変更など)、自動変換ツールも用意されていました。1.0から安定性を約束し、今のやり方になったのです

  • Zigは低レベル作業に使う言語として真っ先に思い浮かびます。ZigをC/C++クロスコンパイラとしても使えるのはとてもクールです

  • たいていの問題は、単にドキュメント不足またはドキュメントの質の低さから来ているように見えます

    • Zigではあまりにも多くの部分が頻繁に変わるので、ドキュメント化は優先事項ではないようです。Zigのチュートリアルもほとんど「サンプルコード集」のようで(最新のコンパイラで動かない例も多いです)、標準ライブラリの定義も多くは自分でソースコードを読まないといけません。Zigの文法トリックを全部知っていれば、単純な関数は短く論理的で、名前も明確なので書き手には簡単です。Allocatorの概念も、概念としては難しくありませんが、自分で作りたいとは思いません。ただ、複雑な概念になると限界がはっきり出ます。Zigの新しいIOシステムは、まるでJavaのStreams/Readers/Writers構造のように複数のレイヤーで包まれています。簡単な出力のために output.write("hello") のように単純に書けるようにしようとしているのですが、実際には使い方の説明が不足していて混乱を招いています。こういう複雑な型システムを、わざわざ標準ライブラリで表現する必要があるのかも疑問です。Zig全体は明確で簡潔で読みやすいメソッドでできているのに、新しいIOシステムはそれとかけ離れていて直感的ではありません
  • (zigの新システムは)本来は実行境界を分けるためだけの概念だったものを、ランタイムエンジン全体に混ぜ込んでしまったのに、その両者をどう接続するのかを明確に示していないのが問題です