6 ポイント 投稿者 GN⁺ 2025-09-27 | 3件のコメント | WhatsAppで共有
  • メモリ管理において、ZigはRustよりもシンプルで直感的なアプローチを提供する
  • Rustのborrow checkerは強力だが、小さなCLIツール開発には過剰な複雑さと開発者の負担を生みやすい
  • Zigの手動メモリ管理は、適切なツールと多少の開発者規律があれば、効率的なメモリ安全性の確保が可能
  • プログラムの安全性はメモリ安全性だけでなく、予測可能な動作、管理しやすい性能、データ保護など多様な要素が重要
  • Rustは大規模システムに適している一方で、小さく実用的なCLIツールではZigのほうが開発生産性と保守性の面で有利

概要

最近CLIツールを作る際、RustよりもZigを優先して選ぶようになっている。

メモリ管理の基本: スタックとヒープ

  • スタックは、関数パラメータ、ローカル変数、戻りアドレスなどの非常に一時的なデータを保存する、高速で固定サイズのメモリ領域である
  • ヒープは動的メモリ割り当てのための領域で、データの寿命が長い場合やサイズが実行時に決まる場合に使われる
  • スタックは構造的に単純だが空間に制限があり、ヒープは速度や断片化の面で気を配る点が多い

RustのBorrow Checker

  • Rustのborrow checkerはコンパイル時にメモリ安全性を保証する
  • 参照、所有権、ライフタイム(lifetime)といった規則を強制し、nullポインタ参照やダングリングポインタなどのエラーを未然に防ぐ
  • ただし、メモリ安全性はコンパイル時の観点でのみ検査されるため、利用者のミスや複雑な所有権設計そのものをなくしてくれるわけではない

事例: 自作のNotes CLI

  • 個人ノート管理用CLIをRustで書こうとしたが、borrow checkerのために構造を苦労して再設計しなければならなかった
  • 一方Zigでは、アロケータを使うだけで、ポインタベースのインデックス生成や自由な変更・削除がはるかにシンプルに実現できる
  • Rustのborrow checkerには明確な目的があるが、Zigは基礎的なメモリ管理の知識と規律だけでも高い水準の効率と安全性を確保できる

メモリ安全性だけではないCLIツールの安全性

  • プロダクトの真の安全性には、予測可能な動作、エラー発生時の意味あるフィードバック、機密データ保護、攻撃耐性など多様な要素が含まれる
  • RustでもZigでも、メモリ安全性以外の条件を満たせなければ「安全」とは言えない
  • たとえば、CLIがエラー時に黙ってデータを上書きしたり、ファイル権限を誤って設定したりすれば、利用者は深刻な問題に直面しうる
  • CLIツールの安全性

    • 予測可能な動作: 不正な入力や予期しない状況でも、一貫して明確な動作を保証する必要がある
    • クラッシュとデータ破損の防止: エラーを適切に処理し、データ破損や未通知のクラッシュを防ぐ必要がある
    • 性能管理: 大量のデータを処理しても、リソース消費や応答性低下が発生しないようにすべきである
    • 機密情報の保護: 一時ファイルや権限設定への注意が必要である
    • 攻撃耐性: 入力検証、メモリオーバーフロー、インジェクション攻撃などに対する強さが必要である

Rust Borrow Checkerの強みと限界

  • 強み

    • データレースと重複参照の防止: コンパイラが単一のmutable参照と複数のimmutable参照という規約を保証する
    • 強力なコンパイル時保証: メモリ関連のバグの大半が実行前に防がれる
    • 初期バグ発見: 商用サービスや並行性システムでは大きな利点となる
  • 限界と不便さ

    • 認知的オーバーヘッド: 小さなCLI作業でも、所有権・ライフタイム・参照管理を常に考える必要がある
    • ボイラープレート/構造の歪み: RcRefCell のようなラッパー、cloneの多用、構造の再設計など、「問題解決」ではなく「コンパイラを満足させること」に意識が向きやすい
    • 論理バグ/状態バグには無力: メモリ規則しか保証せず、予測可能性、論理エラー、データ完全性は保証しない
    • エッジケースの複雑さ: キャッシュ、グローバル状態、mutable index などではライフタイム衝突が起きやすい
  • 結果として、小さなCLIプロジェクトではRustのborrow checkerが開発者にとって「精神的な税」となり、実際に必要な以上に複雑になりうる

Zigの安全性とシンプルさへのアプローチ

  • Zigは選択的な安全性チェックと手動メモリ管理を基盤としている
  • アロケータ(allocator)の概念を内蔵しており、構造的で予測可能なメモリ使用を実現できる
  • カスタムアロケータも作成でき、プロジェクト特性に合わせてメモリ管理方式を指定できる
  • Zigのdefer構文により、スコープ終了時の自動解放やリソースクリーンアップもはるかに直感的である
  • Rustと違って開発者の責任が強調されるため規律は必要だが、構造的にうまく設計すればメモリ安全性の達成と維持は容易である
  • Zigはコードが簡潔で、ポインタやリスト、インデックスなどの構造変更がRustに比べてはるかにシンプルである
  • Rustのように強く縛られなくても、同水準の安全かつ効率的なコードを実装できる
  • さらに、Zigのcomptime機能は、コンパイル時コード実行、テスト、最適化に大いに役立つ

開発者体験(Developer Ergonomics)の重要性

  • **開発者体験(ergonomics)**は、言語の文法、ツーリング、ドキュメント、コミュニティまで含む要素である
  • Rustは非常に厳格な規則によって最終的にメモリ安全を保証するが、過度な規則やceremonyは生産性を低下させる
  • Zigは開発者主導の設計を重視しており、コードをより簡単に、速く、修正しやすく、理解しやすく書ける
  • Zigは直感的なコード、速い反復、低い認知負荷によって、開発者がツールと戦わず問題解決に集中できるようにする
  • Zigは開発者を信頼し、適切なツールと選択肢を与える一方、Rustは過度に監督的で制約が強いと感じられることがある
  • 開発者を「ミスから守る」ことよりも、自らのミスを通じて学び成長する機会を保証することが、開発者に優しい環境である

結論

  • 大規模・マルチスレッド・長時間稼働システムなど、Rustの強みが最大限に生きる分野では、Rustが依然として最良の選択である
  • しかし、小さく実用的なCLIツールには、Zigの軽量さ、シンプルさ、素早い実装と保守性の高さのほうが適している
  • メモリ安全性は安全性というパズルの一部にすぎず、予測可能な動作、保守性、堅牢性といったCLIツールに不可欠な要素は、Zigのほうがより達成しやすい
  • 結局重要なのは「より良い言語」ではなく、**自分に合ったワークフローとプロジェクト特性に適した「ツールの選択」**である
  • Zigは「メモリ安全性 + 低い認知コスト + 開発者親和性/生産性」が組み合わさった、小さなツール開発にぴったりの言語である

3件のコメント

 
shakespeares 2025-10-05

まだRustよりエコシステムが安定していないように思います。

 
bus710 2025-09-27

Zig は新しいバージョンでの破壊的変更がかなり頻繁なので……小さなプロジェクトでも、できるだけ CI を付けて継続的に管理したほうがよさそうですね。

 
GN⁺ 2025-09-27
Hacker Newsの意見
  • Zigの利点は、C開発者のような発想を保ったまま考え続けられる点にあると思うが、ある程度は単に慣れの問題でもあると思う

    • Rustに十分慣れた開発者は、もはやborrow checkerと格闘しない。すでにそういうコード構造で考えるようになっている

    • Rustでは「オブジェクトスープ(object soup)」のようなアプローチはうまく機能しないが、だからといってそれが本質的により簡単な方法だとは思わない。ただ、私たちが慣れているから簡単に感じるだけだ

    • ergonomics(使い勝手)は測定したり数値化したりしにくい、という前提を受け入れると、この種の議論はずっと曖昧なままになる

      • 「この椅子は壊れないから安心して座ってくれ。ただ少し座り心地が悪くて重いかもしれないが、ほとんどの人はすぐ慣れて不便を感じなくなるだろう」という話に似ている
      • 元記事の「Rustは開発者体験が不便だがメモリ安全性を与え、Zigはより良い開発者体験と少しの注意でメモリ安全性を与えられる」という一文は、結局のところ安全性と使い勝手のトレードオフだ
      • Rustコミュニティは、このトレードオフを率直に認めるべきだと思う。安全性の確保は、常に利便性の低下を伴う
      • これはセキュリティ、生命、日常、ソフトウェア全般に通底しており、多くは曖昧だったり主観的だったりする主張だ
      • 「慣れている人には問題ない」と言ってergonomicの論点を簡単に片付ける人もいるが、それはまるで「それすら難しいなら、あなたの頭が悪いのでは」と言っているようにも見える
    • 「borrow checkerとの戦い」という話は、Rustで語彙的(Lexical) lifetimeしか理解されていなかった時代に由来する

      • 私が2021年にRustを学んだときには、すでに過去の話だった
      • 実際、Python、C、JavaScriptなどしか使ってこなかった人にとって、Rustへの適応は簡単ではないかもしれない
      • 私にとってはすんなり受け入れられたが、多くの人はそうは感じていないようだ
      • ただ、「Rustでは診断メッセージを読みながらコードを直さなければならない」というのは、「勇敢にborrow checkerと戦う」ほど格好よくは聞こえない。現実をもっと勇ましく表現すべきだと思う
    • 私の経験では、熟練したRust開発者はあちこちにArcをばらまいて、ほとんど自動的なgarbage collectionのように使っている

      • 静的メモリ管理はかなり厳格なので、複雑なデータ構造では現実的に厳しい
      • lifetimesのためにたどるべき経路が、人間の認知能力を超えてしまうことがある
    • 熟練したRust開発者でも、Arc、Clone、Copyなどをあちこちで使っているオープンソースのRustプロジェクトを多く見てきた

    • Zigの利点は、Cのように慣れた感覚で開発しながらも、言語やツールチェーン側で安全性を補う機能があることだ

      • たとえばZigのoptionalのおかげで、nil dereference問題を避けられる
      • デバッグやテスト時にはdebug/customアロケータを直接渡して、ランタイム検査、メモリアクセス、リソースリークのチェックも簡単にできる
      • 明示的なinterface/traitがない点には不満があるが、採用方法が単純なので実用的だと思う
  • 私は元記事の内容の大半に同意しない

    • RustでもCやZigと同じくlifetime、ownership、borrow scopeについて考える必要はあるが、違いはコンパイラの支援があるかどうかだ

    • どれだけ賢くても、疲れていたり気が散っていたりするとミスをするのが人間だ。ミスを認めることこそ賢明さだ

    • Rustコンパイラが安全だと見なすプログラムの空間は十分広くなく、かなりの頻度でまともなプログラムを拒否する

    • 例: 構造体Fooでbarとbazがそれぞれ文字列のとき、barを可変参照したあとにbazを不変参照しようとするとコンパイルできず、こういう場面ではコード構造を無理やり回避するしかない

    • 反論するとすれば、「実際には問題ない状態なのにコンパイル拒否されるケースを避けるため」に、二番手三番手の設計へコードを変えなければならないこと自体が大きな負担だ

      • そうするとコードベース全体の設計が変わりうる
      • これが、ゲーム開発者のような難しい仕事をしている人にとってRust導入が負担に感じられる理由として説明できる
      • もしRust compilerが誤判定なしに正確にだけ動くなら、ergonomicの面でも最強だろう。もちろん現実はそう簡単ではない
    • 上の例は本当に素晴らしいケースだと思う。自分のブログや記事で取り上げてよいか聞きたい

    • 上のコードを見て、むしろ自分の主張に前より自信がなくなった

  • すべてのプログラムが必ずしもそこまで「安全」である必要はないことを、私たちは覚えておくべきだ

    • 私たちは多くのUnsafeなソフトウェアを楽しみながら育ってきた。Star Fox 64、MS Paint、FruityLoopsなどだ

    • Zigの作者Andrew Kelleyも、音楽制作ソフトウェア(DAW)の開発環境がなかったからZigを作った、と読んだことがある。Zigはそういう創造的なソフトウェアによく合うと思う

    • メモリバグに敏感な人はRustを使えばいい

    • Super Mario Worldも、メモリバグがあったからこそより面白かったと信じている

    • 「安全」とは、「自分のプログラムが意図どおりに動作する」という意味の短縮形だ

      • 意図しない非論理的なコード(semantic gibberish)は、目的達成に不利だ
      • 芸術的に難解なコードをあえて書く場合(IOCCC、ハッキング、コード詩)もあるが、そういう場合は慎重にcraftしなければならない
      • Rustでもescape hatch(unsafe)によって、意図的にそうしたものを実装できる
      • 本文の主張は、意図せず意味不明な(gibberish)コーディングが利点だということだが、これは簡単には同意しがたい
      • もし欠点なしにすべてのコードを安全にできるなら、誰だって望むのではないか?
      • Super Mario Worldのスピードランなどは、バイナリパッチでも実現できるし、入力によるメモリ操作だけが唯一の面白さだとは思わない
    • 少し混乱しているのだが、私の意見がよくないと言われる理由は、メモリ安全が重要ではないと言っているように聞こえるからなのか気になる

  • borrow checkerの価値が過小評価されているのが残念だった

    • Rustのborrow checkerは、無効なメモリアクセスが起きないことをコンパイル時に保証してくれる

    • もちろん、コンパイラの規則に従うコード構造へ変えなければならない不便さはある

    • Rustを個人的に使うとき、lifetime annotationが間違っているとは感じなかったし、面倒なchoreのようではあったが、すぐに慣れた

    • unsafeを使わない限り、Rustでは2つのスレッドが同時に同じメモリへ書き込めない

    • 「なぜCLIツール開発でZigのほうがより実用的に感じられるのか」には共感できない。CVE(脆弱性)防止の面では、Rustは依然として強みがある

    • 実際、私は大半の作業をGC言語でも十分こなしており、他言語に貢献するときもRust、Zig、C/C++のどれでも構わない

    • CLIツールが特別だというのか?

      • 一般にCLIツールは規模が大きくないか、個人開発のことが多く、管理しやすい
      • ZigやCは大規模コードベースにはあまり向いていないかもしれず、より複雑なプロジェクトではbabysitterが必要になる
      • これに似た昔の議論として、Javaも「babysitter言語」と呼ばれたことがあるが、実際には多くのコードベースでそれが必要だ
    • unsafeを使わなければ2つのスレッドが同時に同じメモリを書けない、という話はそこまできれいには割り切れない

      • 私がコンパイラに期待する支援は、主としてメモリアクセス順序(memory ordering)の問題の解決だ
      • Rustは、この種の部分でraceが起きてもunsafeではなくsafeに分類している
  • Rustでbacklinksを実装するのが非常に複雑だという点には同意する

    • Rc、Weak、RefCell、.borrow()などを使えば可能だが、簡単ではない

    • 短時間で終了するプログラムなら、arenaアロケーションも手だろう(CLIツールとはそういうものを指しているように見える)

    • Rustは巨大で、マルチスレッドで、長時間稼働するアプリケーションで本当に力を発揮する

    • 実際、私はRustで大規模なメタバースクライアントを書いたことがあるが、数十本のスレッドを24時間回してもメモリリークもクラッシュもなかった

    • 同じことをC++でやるなら、QAチームとValgrindのようなツールが必須で、スクリプト言語は性能面で遅すぎる

    • 私もRustで、地球の曲率や重力偏差まで反映した物理シミュレーション飛行機を作った

      • 5時間、22時間の経路テストを何年も問題なく回せた
      • 7年間Rustを使ってきてクラッシュ経験はごくわずかで、C/C++は過去のレガシーコードを直すときだけ使う
  • Zigは魅力的だが、Dもまだ存在していて、個人的にはDのほうが自分の望むC/C++代替に感じる

    • Zigの文法はどこかしっくりこず、Rustはすでにエコシステムの中心的存在になっている

    • Goもさまざまな言語ツールで高いシェアを持っており、AI分野ではPythonの次によく使われている

    • Rust以前にはGo vs. D論争があり、私もDの教材まで買ったが、結局Goに流れた

      • 私にとっては標準ライブラリのほうがずっと実用的で、int64のようなデータ型名のほうが直感的だった
    • Dは良いが、普及を後押しするキラーアプリが出てこない

  • RustやZigでわざわざCLIツールを書く理由がよく分からない

    • ボトルネックはI/Oであって、GCが遅いからではない

    • GCの問題は、ゲームやDBのようなメモリ集約型でない限り論点ではないと思う

    • メモリ安全性の議論よりも、GCのない言語を選ぶべき理由をもっと考えるべきだ、という点を強調したい

    • もし「ノーGCが楽しいから」で使っているなら、それだけで十分で、議論は不要だ

    • 起動が即時であること(実行遅延がないこと)は非常に有益だ

      • ラッパーツールを作るとき、何千回実行しても負担がない
      • Pythonのような環境の配布の複雑さがなく、配布も容易になる
    • GoでCLIを作るのはとても良かった(Go言語自体は好みではないが)

      • Python製CLIは依存関係が多いと配布が面倒で、Rust/ZigもGoのように静的バイナリ配布が簡単なので人気がある
    • sum type、パターンマッチング、asyncサポートがある言語を最優先で選ぶ

      • Rustでなくても、エラーをコンパイル時に捕まえてくれる機能は良い
    • GCなしの開発がゲーム分野に限られるという指摘について

      • 実際には、多くのモバイルゲームもUnity+ il2cpp環境のようにGCを使っており、しかもGC性能がそれほど良くない場合が多い
    • GC論争は、ある種のバンドワゴンのように感じる

      • 50年前にはすでにGCベース言語でInterlisp、Cedarなどの大型ワークステーションが成功裏に作られていた
      • 今日のハードウェア性能は1970年代のCLIやElectronアプリよりはるかに高いのに、効率的な活用が足りていない
  • Rustの組み込みborrow/reference機能で簡単なノートツールを作ったが、思ったほど複雑ではなかった

    • notesリストのインデックスを保存し、マップでつなぐ構造を想像すれば、速度差もほとんどなく、安全性の欠点もない

    • インデックスを間違えても範囲外エラーで捕まえられるので、カーネルメモリを上書きするよりはるかにましだ

    • printfデバッグのときもずっと簡単で直感的だ

    • raw pointerやreferenceは、通常allocatorやasync runtimeのように本当に必要な場面にだけ使い、一般的なロジックではindex-basedのほうが向いている

    • よく知られているように、Rust asyncでself-referential structが使えずPinまわりの問題が生じるのも、ここに理由がある

    • vecに保存された値へのポインタは、reallocなどが起きると無効になるため、この場合はMiriですぐエラーになる

  • C++開発者の私が安全な言語を探すなら、Swiftがいちばん適していると感じる

    • 馴染みのある、あるいは似た言語ほど適応は早い

    • Swiftは最近クロスプラットフォーム対応も強化されており、現役のC++標準委員会の人たちも複数参加している

    • ただしAppleとの結びつきや、ネイティブUIフレームワークの不在などから、Apple以外の陣営への広がりは相対的に弱い

    • Swiftがもっと人気を得てほしい

    • SwiftとZig/Cを比較できるリソースがあれば、紹介してもらえるとうれしい

  • Zigも、ある程度の注意を払えばメモリ安全なソフトウェアを作れると言われるが、実際にはCでも一定の規律を守れば同じ結果になる

    • 結局、この「少しの規律(訓練)」が現実では足りないから問題が起きる

    • Zigはさらに次の問題も解決する

      • Out of bounds access(全CVEの70%を占める)
      • null pointer dereference
      • 型安全性
      • Cよりはるかに優れており、use-after-freeのようなバグも、境界を越えない限りずっと避けやすい
      • Zigの優れたクロスプラットフォームビルドシステム、comptime最適化、C++/Rustより数十倍速いビルド時間も強みだ
      • まだ標準ライブラリは不十分で細かな問題も残っているが、perf-orientedなプログラムでは将来性が明るいと思う
    • Cが50年以上にわたってこのdisciplineの問題で失敗してきたなら、それは「少林の道」以上に難しいことだ