/note/tech

Rust is a disappointment

要約:

■ 1. Rustに対する著者の立場

  • 著者の姿勢:
    • かつて自分をRustヘイターと呼んでいた
    • しかしそれはStack Overflowの調査で最も愛されている言語としてRustがトップになるようなファンボーイ的傾向を相殺するためだった
    • C++を嫌う理由は多くあり、著者もC++を嫌っている
    • 多くの人がより良いプログラミング言語を待っていたが、代わりにRustを得た

■ 2. Rustの4つの核心的問題

  • コンパイルが遅い:
    • 単に遅いのではなく非常に遅い
    • C++より遅い
    • 年々Rustは数倍速くなったが、客観的には2倍ではなく2桁(100倍)速くなる必要がある
  • 複雑である:
    • C++と同程度に複雑
    • しかしC++にはレガシーがあったがRustにはなかった
    • Arc<Mutex<Box<T>>>のジャングルを毎回通り抜けなければならない複雑さが、実装されるロジックの品質に直接影響する
    • 木を見て森を見ずの状態になる
    • C++も同じ問題を抱えているなら、結局言語を切り替える意味は何なのか
  • メモリ安全性はそれほど神聖ではない:
    • 実際、多くのアプリケーションではクラッシュするよりも誤動作する方が良い
    • 特にRustが存在したがっている組み込みの世界ではそうである
    • Rustでは99.999%の信頼性を得ることはできない
    • 常にクラッシュする
  • 可変共有状態の処理において:
    • GUI、データベース、ステートフルサービス、OS/ハードウェアなど多くの可変共有状態を扱う場合
    • ネイティブのRustメモリモデルのパフォーマンスは劣る
    • 非ネイティブのunsafeを使うとコンパイルが遅く、複雑性が高く、結局メモリ安全性もなくなる
    • 重い可変状態の仕事に対してRustは実質的に無意味になる

■ 3. C++の問題点

  • 未定義動作の遍在:
    • 未定義動作(UB)は言語の基本的な側面
    • 単にUBに遭遇するのではなく、言語全体がUBの上に構築されている
    • 配列のインデックスアクセスで即座にUBに遭遇する
    • 言語が範囲外アクセスをチェックしないため
    • 多くのUBはパフォーマンスの理由でさえ正当化されない
    • Cから引き継がれC++で増幅された完全にずさんな設計
  • C++の具体的な問題:
    • 暗黙の型変換、暗黙のコピー、暗黙のコンストラクタ、暗黙のオブジェクトスライシング、ほぼすべてが暗黙的
    • 関数オーバーロード(暗黙的)、特にSTLでの遍在性を考慮すると
    • 例外を後付けとした非統一なエラーハンドリング
    • Cから40年経っても#includeでテキストファイルを取り込んでおり、One Definition Ruleはコンパイラによるチェックがほとんどない
    • パラダイムの不健全な組み合わせ(子孫クラスでジェネリック関数をオーバーライドする幸運を祈る)
    • ジェネリックプログラミングの中核メカニズムとしてのSFINAEの厄介さ
    • T、T&、T*、std::optional、std::unique_ptrで似たようなものを記述するが、それぞれ独自の方法で壊れている。その上にconstを載せる
  • 結論:
    • C++は複雑で、安全でなく、コンパイラが遅い
    • Rustはこれらの問題をどのように修正し(あるいは修正していないか)

■ 4. 遅いコンパイルの詳細

  • 設計上の問題:
    • 一時的な問題ではなく設計によるもの
    • Rust開発チームは毎回コンパイル速度を犠牲にしてきた
  • 最適化の努力:
    • Rust FAQでは、より良いフロントエンド、MIRなど多くの最適化努力があると説明している
    • しかしMIRの取り組みは2015年に開始されたが、依然としてコンパイルを大幅に高速化できていない
    • コンパイラチェックは高速化している
  • 本質的な問題:
    • Rustを高速にコンパイルすることは不可能
    • 問題はHaskellのような類似のジェネリクス多用言語に固有のもの
    • Rustは議論の余地があるがC++よりもHaskellに近い
    • テンプレートを多用するC++コードと同じ遅いコンパイルの問題を示す
  • 具体例:
    • for i in 0..limit {}を実行すると、単に反復するのではなく、範囲を作成し、イテレータを作成し、それを反復する
    • すべてが具体的な型に単相化され、個別に最適化される必要がある
    • 最適化されていないRustコードは非常に遅く、デバッグにさえほとんど使用できない
  • ボローチェッカーの影響:
    • その上に非オプションのボローチェッカーを載せると、非常に遅いコンパイラになる
    • ボローチェッカーは容赦ないので、何度も再コンパイルすることになる

■ 5. 複雑さの詳細

  • 回避不可能:
    • 「コールドパスの高レベルコードを書いているのでパフォーマンスは必要ない、ライフタイム処理の深みに入る必要はない、単に高レベルのロジックを書きたい」という姿勢は取れない
    • Rustの1行を書くたびに低レベルの細かい点に強制的に押し込まれる
  • ガベージコレクタの不在:
    • RustにはGCがなく、今後もない
    • すべてのデータを所有権のツリーに半手動でパックする必要がある
    • 数行のコードを書くだけでも所有権、借用、トレイトに精通している必要がある
  • 高レベルロジックの困難:
    • Rustで高レベルのロジックを書くのは非常に困難
    • 多くの初期Rust採用者が実際にはパフォーマンスにそれほど敏感でないサービスにはNode.jsやGoに戻っている
    • 高い複雑さと遅いコンパイルの組み合わせにより、純粋なRustで複雑なものを書くことが非現実的になる

■ 6. メモリ安全性の詳細

  • Rustの妥協しない2つのもの:
    • パフォーマンスとメモリ安全性
    • Rust設計者はメモリ安全性に関して行き過ぎた
  • コンテナの実装:
    • コンテナは実際にはunsafe関数で実装されている
    • チューリングマシンモデルでは完璧な正しさは不可能
    • 慎重に配置されたunsafeなビルディングブロックから安全なプログラムを構築する必要がある
  • 他言語との比較:
    • Node.jsとGoは実用的に安全な言語と見なされている
    • Rustは正気さと実用性をメモリ安全性のために犠牲にした
    • そして結局どれも得られず、依然として100%メモリ安全ではない
  • 実用性について:
    • 多くのユースケースは完璧なメモリ安全性を必要としない
    • リモートコードを実行したり秘密を漏らしたりしない安全でないプログラムを実装する方法がある
    • ユーザーデータを破損させ散発的に動作するだけ
    • ペースメーカーが停止した場合、被害者に「クラッシュ時にメモリは破損していなかった」と伝えても慰めにならない
  • Cloudflareの障害事例:
    • unwrap()関数でのクラッシュによって引き起こされた最近のCloudflareの障害があった
  • 著者の最も強い主張:
    • Rustはメモリ安全で信頼性がない
    • メモリ安全性の代価は開発者の正気さに加えて信頼性だった
    • 言語設計者は行き過ぎた
    • メモリ安全性という抽象的な原則のために中核的な実用的品質を犠牲にした
    • Haskellの設計者が純粋性のために実用性を犠牲にしたのと同様
    • RustとHaskellの類似性を繰り返し指摘する理由

■ 7. 可変共有状態の詳細

  • Rustでの可変共有状態:
    • 可能だが、Rustで可変共有状態を使用することは意味がない
    • ほとんどの利点を失い、Rustのすべての欠点だけが残る
  • 成功したRustプロジェクト:
    • ほぼすべてが共有の読み取り専用状態、一方向データフロー、非循環データ構造を採用している
    • rustcコンパイラ、mdbookとpulldown-cmarkのMarkdownツール、ステートレスハンドラ用のActixとAxum、追記専用ブロックチェーン、シングルスレッドWASM
    • モデルはHaskellに非常に似ている
    • Haskellもパーサー、ステートレスハンドラ、ほぼ非インタラクティブなCLIツールで優れており、ブロックチェーンで使用されてきた
  • Software Transactional Memory:
    • Rustの初期プロトタイプには安全な並行性のオプションとしてSTMがあった
    • しかしSTMにはパフォーマンスペナルティがあり、単純だが重要なランタイムサポートが必要
  • 共有可変状態に踏み込むと:
    • メモリ破損は例外ではなくルールになる
    • 破損を処理する必要があり、単にクラッシュすることはできない
    • ボローチェッカー、所有権は役に立たない
    • GCのようなアルゴリズムなしで循環グラフの所有権を分析することはできない
  • Sync/Send、Mutex、Arc:
    • ロックするか、CPUキャッシュをひどく乱すので、マルチスレッド通信には非効率
    • 少なくとも集中的なものには
    • 安全だが非効率
    • Rustの最初の妥協しないもの(パフォーマンス)を破壊する
  • 結論:
    • 共有可変状態に踏み込んだ瞬間にRustのすべての利点を失う
    • Rustの主要なコンセプトが共有可変状態を決して使用しないことだったことを考えると合理的

■ 8. GUIとRustの問題

  • GUIは可変共有状態:
    • そのためRustで大きなGUIプロジェクトは見られない
  • Zed IDEの例:
    • 何年も経ってもまだベータ版
    • 開発者がボローチェッカーのジャングルを突破してロジックがバグだらけであることに気づき、まだ何十もの他の機能を実装する必要がある苦痛がほとんど感じられる
  • まだ見られないもの:
    • 大規模データベース
    • スケーラブルなステートフルサービス
    • オペレーティングシステム
    • 少なくとも重要なLinuxモジュール

■ 9. 結論:Rustの評価

  • Rustは良いのか悪いのか:
    • どちらでもない
    • 開発に何千人月も費やされた平凡なプログラミング言語
    • この事実だけでRustは実用的なツールになる
    • 棚から取り出してそのまま使用できるため
  • 具体例:
    • このブログはRustで書かれたZolaで生成された
    • 使用するためにRustのコードを1行も書く必要がなかった
    • Zolaは不変データの一方向フローを持つ非インタラクティブな性質のためRustに適している
  • 著者の願い:
    • 「Rustは最高のプログラミング言語だから、みんな開発をRustに切り替えるべきだ」と叫び回らないでほしい