unsafeが存在するという理由だけで Rust を排除し、Fil-C だけを安全とみなす基準は、実際のソフトウェアの適用範囲と技術的トレードオフを見落としている- Fil-C は C/C++ の不正なメモリアクセスをパニックに変えるが、ABI 非互換性、一部状況での数倍の性能低下、GC 導入というコストを伴う
- Android の約 500 万行の Rust コードでは、リリース前に修正された潜在的なメモリ安全性脆弱性が 1 件見つかり、100 万行あたり 0.2 件と推定された。これは C/C++ の過去データである約 1,000 件より 1,000 倍以上低かった
- すべてのプログラムで問題の 99.9% を防ぐ技術と、90% のプログラムで 100% を防ぐ技術のどちらか一方だけを選ぶ必要はなく、Fil-C の制約を受け入れにくいソフトウェアには Rust のような代替が適している
- メモリ安全性は 性能・ABI・GC・データ競合防止をあわせて考慮すべきであり、Rust でも不十分だと批判するなら、通常の C/C++ や非 Fil-C の Zig にも少なくとも同じ基準を適用すべきである
Rust と既存のシステム言語の責任モデル
- 非 GC のシステムプログラミング言語におけるメモリ安全性の議論は、主に Rust と C/C++/Zig の責任モデルの違いを中心に進められてきた
- Rust は、安全でありうる一部のプログラムまで拒否するコストを受け入れつつ、メモリ安全性問題を引き起こしうるプログラムのコンパイルを防ごうとする
unsafeは、生ポインタのデリファレンスなどを許可し、一部の保証を迂回するための抜け道である- C 系言語は、メモリ安全性の保証の大半をプログラマに委ねている
- C++ の RAII とスマートポインタ、Zig の
deferのように言語ごとの支援水準には違いがあるが、不正なメモリアクセス自体を原則として遮断するわけではない
Fil-C が加えた選択肢
- Fil-C は、C と C++ のコードをメモリ安全に実行する新しいアプローチを提供する
- 範囲外アクセスや解放後使用のような不正なメモリアクセスが発生すると、パニックを起こす
- GC と、ポインタがアクセスできるメモリを追跡する InvisiCaps を組み合わせている
- Zig にも、Fil-C に着想を得た 新しいコンパイルモード が提案されている
- 人気のある C/C++ プロジェクトの一部が Fil-C でコンパイルしたリリースを提供すれば、メモリ安全性脆弱性を減らすための選択肢が増える可能性がある
Rust を安全でないとみなす基準
- Fil-C の開発者は Twitter で、
unsafeによって一部の保証を迂回できることを理由に、Rust をメモリ安全な言語ではないと評価してきた - Zig の開発者 Andrew Kelley も、関連イシューのタイトルで Fil-C に着想を得たモードを「Rust と違って実際にメモリ安全な」コンパイルモードと表現している
- 一部の議論では、Rust ユーザーが本当にメモリ安全性を重視するなら Rust を捨てて、より安全な Fil-C を広めるべきだと求めている
- この基準は、Fil-C の現実的コストを除外したまま Rust と Fil-C を比較しており、Rust コミュニティがしばしば受ける狂信的だという批判と似た態度を示している
Fil-C の適用上の制約
- Fil-C は、コストなしの ドロップイン代替ではない
- 非 Fil-C でコンパイルされたプログラムと ABI 互換ではない
- 一部の状況では数倍遅くなることがある
- GC を導入する
- 単純なユーティリティのように性能低下を体感しにくい、または動的リンクが不要なプログラムでは、こうした制約が決定的でない場合もある
- 逆に、GC や ABI 非互換性を受け入れられない人気プロジェクトも多く、現在の形の Fil-C を適用しにくいプログラムはしばしば Rust に適している
実際の Rust コードにおける脆弱性データ
- Rust の実質的な安全性を判断するためのデータはまだ多くないが、Rust ソフトウェアで悪用可能なメモリ安全性脆弱性が多数見つかっているわけではない
- Android の 500 万行超の Rust コード では、潜在的なメモリ安全性脆弱性が 1 件見つかり、リリース前に修正された
- 推定脆弱性密度は 100 万行あたり 0.2 件である
- Android の過去の C/C++ データは 100 万行あたり約 1,000 件である
- Rust コードの密度は C/C++ より 1,000 倍以上低く追跡されている
- プロジェクトごとに数値は異なりうるが、実環境で Rust がメモリ安全性問題の導入リスクを大きく下げることの根拠になる
どちらか一方だけを選ぶ必要がない理由
- すべてのプログラムで問題の 99.9% を防ぐ技術と、90% のプログラムで問題の 100% を防ぐ技術を比較する仮想的な選択は、適用範囲と予防水準の両方が重要であることを示している
- 実際の比率は分からないが、この二つのアプローチのどちらか一方だけを選ぶ必要はない
- トレードオフを受け入れられる C/C++/Zig プロジェクトは Fil-C バイナリを提供できる
- Fil-C を使えないソフトウェアは、メモリ安全性脆弱性のリスクを完全に、または大部分取り除く言語で書くことができる
GC を使えても Rust を選ぶ理由
- Go や Fil-C のような GC ベースの選択肢があっても、Rust を使うのは妥当である
- GC ベースの言語で書けるプログラムでは
unsafeが不要なことが多く、unsafeが必要なプログラムでは GC を使えないことが多い - 小さなメモリ安全性リスクよりも、データ競合防止のような他の言語保証や機能を重視することもありうる
- Fil-C は既存の C/C++ のメモリ安全性脆弱性をクラッシュに変える
- セキュリティ脆弱性よりはましだが、100 万行あたり約 1,000 件という過去の密度がそのままなら、修正すべきクラッシュは依然として多く残る
- 過去には、攻撃者がプログラムをクラッシュさせる能力を利用したセキュリティ脆弱性もあった
一貫したメモリ安全性基準
- Rust の 100 万行あたり 0.2 件すら許容できないなら、通常の C/C++ と非 Fil-C の Zigにも同等かそれ以上に厳しい批判を適用すべきである
- Rust より安全性の低い代替を許容しながら、
unsafeだけを理由に Rust を排除する態度は、メモリ安全性絶対主義を一貫して適用できていない
1件のコメント
Lobste.rs の意見
OPが不快に受け取ったらしい Andrew Kellyの発言 は、ZigがC/C++依存全体まで逃げ道なく 完全にメモリ安全な実行ファイル としてコンパイルでき、ポインタ追跡の頻度に応じて性能コストがおよそ1〜6倍だという内容
投稿者はこれをRustへの攻撃と受け取り、記事の終盤ではZigを攻撃しているが、Fil-CとZigの新しいビルドモードはエコシステムへの前向きな貢献であり、Rustとは異なる設計上の地点とトレードオフを提供している
Zigチームが好むデータ指向プログラミングに従えば、性能コストを1倍近くまで下げられるという意味だと解釈している
これを不必要に挑発的だと読むのも無理はなく、Andrewはその後見出しをやや穏当なものに変えた
いつもとは逆に「お前たちの言語はメモリ安全ではない」という軽いからかいが向けられると、Rust開発者たちの反応の規模 はかなり大きかった
私もRustはとても好きだが、公平に受け止める必要がある
さらに言えば、seL4の 形式検証されたC はもっと安全だ
Zigには、メモリ割り当て失敗時に正しく終了しやすく、コンパイルも速いという利点がある
Fil-CがRustよりメモリ安全かどうかは分からないが、私の用途では ガベージコレクタとC ABI非互換性 が決定的な障害だ
シングルスレッドにすれば競合状態を避けられ、ガベージコレクタを入れればメモリ安全性を得られるが、Rustはその2つの妥協なしに両方を提供している点がよい
メモリ安全性を非常に重視するので、Fil-Cと ZigのFil-C ABI実装 は当然の選択だ
C依存のあるRustプロジェクトは安全性保証が弱まり、純粋なRustだけを使うこともできるが不便だ
Rustも Fil-C ABI を実装してC依存を安全にビルドし、Rustとリンクできるようにすべきで、これがなぜ論争的なのか分からない
同様の機能を実装するなら、デバッグビルドでのみFFIコードを改善する補助手段として使ってほしい
ガベージコレクタを置き、すべての演算をランタイムで検査する方式は、あらゆる用途に合うわけではない
Fil-Cを単なるユーティリティ用だと片付ける前に、Software Should Workカンファレンスの Fil-C発表 を見る必要がある
発表者はユーザー空間全体とOpenOffice ImpressまでFil-Cで構成したLinuxノートPCで発表した
一部のC/C++プログラムには適さないかもしれないが、投稿者が考えているほど おもちゃ技術 には見えない
Python出身なので、メモリ安全性は前提条件だったし、Rustを選んだ理由は3つあった
第一に、強力な型システムでコンパイル時の正確性を確保でき、
#![forbid(unsafe_code)]と cargo-geiger を使った依存関係監査で得られるメモリ安全性は、その中で最も興味の薄い表現だった第二に、一度安全に書いたコードを複数の言語や実行環境で共有しやすい エコシステム を提供している
第三に、当時の
try!(x)のように高水準コードを楽に書ける糖衣構文があるZigとFil-Cは、型状態パターン やnewtypeなどで不変条件を型システムにエンコードし、論理エラーをコンパイラに捕まえさせる能力 を満たしていないように見える
Fil-CのABI非互換性も、共有WebホスティングのCPythonのような既存ランタイム向けに安全なコンパイルモジュールを書くとき問題になる
comptimeがRustより表現力に優れるコンパイル時間と冗長さ、エコシステムの大半がそこまで試していないというトレードオフはあるが、実際に可能でかなり面白い
Fil-Cのような技術がなぜ 20年前には登場しなかったのか 気になる
だが、誰もCPUやメモリ、つまりコストでその代償を払いたがらなかった
2004〜2018年にはアイデアはあったが、メモリ安全なCという発想自体を愚かだと見ており、2018〜2023年には考えを変えたものの、極端な互換性を達成する方法を見つけられなかった
2023〜2024年初期のFil-Cは互換性も性能もはるかに低く、2024年末の InvisiCapsのブレークスルー により、現在の高い互換性とまずまずの性能を得た
2018年ごろに考えを変えたきっかけは、GPUで使われるC変種がメモリ安全なCの単純な形だという観察だった
「Rust陣営が本当にメモリ安全性を重視するなら、より安全なFil-Cを支持してRustを捨てるべきだ」という話を最も善意的に解釈すると、いまやFil-Cがあるのだから、世界をRustで書き直そうという努力をやめてC/C++に戻り、以前のように統合されたライブラリエコシステムを維持しよう、という意味になる
RustライブラリをC/C++から使えるとしても、それを望まない開発者たちがいるので、Rust利用者がよりよい解決策を認めて撤退すれば分裂が消える、という理屈だ
しかしFil-Cには、ガベージコレクタとx86-64 Linux専用サポート というRustにないトレードオフがある
メモリ安全性以外にも、Cargoとグローバル名前空間がないことはRustを使う重要な理由であり、言語陣営間の分裂がより広い文化戦争と絡み合っている状況は残念だ
誰もが喜んで使えるライブラリを作りたいだけだ
C開発者の中にはC++ライブラリを望まない人もいるかもしれないし、ZigやOdinまで登場したので、Rustが消えても分断は残る
Rustが特別に別種の分断を生み出しているのか気になる
comptimeとして保ったままの Rust→Zig高水準トランスパイラ を開発中だRustはより多くの制約条件をエンコードできるので、元の言語として理想的だ