Rustでゲーム開発を3年続けた末に離れる
(loglog.games)- Rustに慣れればすべての問題が消える、という主張について
- Rustに慣れても根本的な問題は消えない
- ゲームは複雑な状態機械であり、要件が継続的に変わるため、Rustの静的で過剰に検査する特性とは合わない
- コードを継続的にリファクタリングしなければならない問題は self-inflicted である
- Borrow checker によって発生する大規模リファクタリングの問題
- Rustは他の言語よりも頻繁にリファクタリングを強いる
- ゲーム要件が頻繁に変わる状況では borrow checker と格闘することになり、コードの再構造化が必要になる
- リファクタリングが良いコードを生むという主張には同意しにくい。良いコードはアイデアを反復し、試しながら作られるものだ
- 間接化(Indirection)は一部の問題しか解決せず、開発生産性を下げる
- Borrow checker の問題を解決するために間接化を使うと、コードロジックが分離されて複雑になる
- ゲームでは連動するイベント、特定のタイミング、多くの状態管理が必要だが、間接化はそれを難しくする
- Rustでは単純なコードでも間接化のために冗長に書かなければならない
- ECS(Entity-Component-System)は間違った問題を解決している
- ECSの利点は実際には generational arena の利点である
- ECSを dynamic composition、structure of arrays、Rust borrow checker の解決策、dynamically created generational arenas など様々な観点から見ている
- しかしゲーム開発でECSが最適な方式なのかは疑問だ。パフォーマンスやコンポジションより、ゲームロジックにもっと気を配るべきだ
- 一般化されたシステム(Generalized systems)は面白いゲームプレイにつながらない
- 過度に一般化されたシステムを使うと、退屈なゲームプレイが生まれる
- 良いゲームは細かな相互作用を注意深く設計し、VFXを同期させ、プレイテストと実験を繰り返し、早く公開してフィードバックを得ることが重要だ
- Rustは高速なプロトタイピングと反復(iteration)に適していない価値観を追求している
- ゲーム開発で重要なのは迅速なプロトタイピングと反復だが、Rustの価値観はその逆である
- ゲーム開発の核心はゲームそのものとプレイ体験であり、クラッシュしないコードではない
- ゲームコードの保守性はインディーゲームではそれほど重要な価値ではない。重要なのは反復速度(iteration speed)だ
- Rustは問題を避けることに執着するあまり、本当に重要なことを見失っている
- Procedural macros はリフレクションにはるかに及ばない
- ゲーム開発ではシステムコード、ゲームプレイコード、UI、VFX、オーディオ、ツールなど多様な領域のコードを書く必要がある
- Rustでは簡単な「オブジェクト出力」ですらコードを直接書くか、procedural macros を作らなければならない
- Procedural macros は宣言的マクロに比べてはるかに制約が多く、コンパイル時間も長い
- C#のリフレクションは非常に使いやすく、パフォーマンスが重要でない場面では迅速な開発を可能にする
- Hot reloading は反復速度向上に重要な役割を果たす
- Hot reloading は immediate mode UI/描画、デバッグ、ゲームプレイ定数のチューニングなどに非常に有用だ
- Rustにも
hot-lib-reloaderはあるが、完璧ではなく、計画と先見性が必要なため創造的な使い方が制限される - ゲーム開発者は単純な struct の再読み込み以上の高次のツール群を求めている
- 抽象化は選択ではなく必須だ
- 実例)UIの状態に応じて異なる動作をしなければならない場合、Rustではリファクタリングが必要になったり、clone を乱用することになる
- ビジネスロジックが変わるのではなく、コンパイラを満足させるためにコードを変えることが多い
- Rustには「これらのフィールドを持つ型」のような構造的型システムがないため、リファクタリング時にコードの複数箇所を修正しなければならない
- RustのGUI事情は最悪だ
- ゲームUIで重要なのはデータバインディングやリアクティブ更新ではなく、見た目をカスタマイズすることだ
- ゲームUIに必要なのは美しいGUI、カスタムスプライト、アニメーション、ベクターシェイプ、パーティクル、エフェクト、フラッシュなどだ
- 現在、ゲームGUIに特化したRustライブラリはない
- Orphan rule はオプショナルであるべきだ
- Orphan rule は安全性のために開発生産性を大きく下げている
- ライブラリではないアプリケーションコードでは orphan rule を無効にできるべきだ
- コンパイル時間は改善されたが、proc macros を使うと依然として遅い
serdeのような proc macros はコンパイル時間を大きく増加させる- 20〜30秒以上かかる incremental build では細かなチューニングが非常に難しい
- Rustのゲーム開発エコシステムは誇張されている感じがする
- 実際にゲームを作っている人は多くない
- WebサイトやREADMEが華やかで有名に見えるプロジェクトが、実際にはそうではないことが多い
- 実際にゲームを作りたいなら
godot-rustを勧める。pure Rust ソリューションに固執せず、成熟したエンジンの助けを借りたほうがよい
- ゲームはシングルスレッドなので、グローバル状態が扱いにくい理由づけは誤っている
- Rustでグローバル状態を使うのは非常に不便だ(
static mut、AtomicRefCell、Rcなど) - しかしゲームはバックエンドサービスと違ってすべてがシングルスレッドなので、こうした不便さは必要ない
- Bevyが並列システムを導入したのは誤りだと見ている。高速なパフォーマンスのためだったが、ユーザーが順序を常に明示しなければならないなど、かえって不便さを増している
- Rustでグローバル状態を使うのは非常に不便だ(
- 動的 borrow checking はリファクタリング後に予期しないクラッシュを引き起こす
hecsを2年間使う中で、query が重なってクラッシュする事例を頻繁に見たRefCellを使うときも、2か所で.borrow_mut()すると予期しないクラッシュが起きる- コードが悪いというより、Rustが不要なリファクタリングを強いている
- ゲームでは
RefCellが有用だが、Rustはこれをあまりにも難しくしている
- Context オブジェクトの柔軟性が不足している
- Rustではグローバル状態を使いにくいため、context オブジェクトに参照を集めて渡すパターンを使う
- しかしこのとき一部のフィールドだけを借用すると partial borrow のためコンパイルエラーが発生する
- Contextを分割して構造を変えろと言われるが、実際にやりたいのはゲームロジックであって、コンパイラを満足させるために構造を変えるのは時間の無駄だ
Rustの長所
- コンパイルさえ通れば、たいていはうまく動く。特にCLIツールやデータ処理、アルゴリズム記述で有用
- 特別な努力なしに良いパフォーマンスを示す。C#より1.5〜2.5倍ほど速かった経験がある
- Enumの実装が優れている
- Rust analyzer のおかげでIDEの使い勝手が大きく改善された
- Trait システムがよくできている。Orphan rule さえ少し緩和されれば、さらに活用度が高まるだろう
GN⁺の意見
-
Rustに対する意見は全体としてかなり否定的ではあるが、著者が様々な試行を経て到達した結論である以上、真剣に受け止める必要がありそうだ。特にゲーム開発というドメインで実用性と開発速度がどれほど重要か、そしてRustがその面でまだ不足しているという指摘には一理ある。
-
Rustが目指す安全性が、ときに開発生産性を深刻に低下させるという点には同意する。Rustのコードを書いていると、ロジックそのものよりコンパイラを満足させることに多くの時間を使うことがあり、これはRustが根本的にシステムプログラミング向けに特化した言語であることに由来しているように思える
-
一方でゲーム開発は大半がシングルスレッド環境であり、高速なプロトタイピングと反復が重要な領域なので、過度な安全性検査はむしろ害になることもあるだろう。特にRust初期にゲーム開発を試みて挫折した経験談は多く耳にしてきたが、いまなお根本的な問題点は大きく改善されていないようだ。
-
もちろんBevyなど近年登場したゲームエンジンがRustでのゲーム開発のしやすさを高めてはいるが、まだUnityやGodotのような成熟したエンジンには大きく及ばないように思える。この記事のようにRustだけで純粋にすべてを作るより、既存エンジンと組み合わせて使うほうが現実的な代替案になり得る
-
著者が挙げた長所は、ゲーム以外の分野でRustを使うときには非常に実感しやすい。特にシステムプログラミングやWebサーバー開発のような領域では、Rustの安全性と高いパフォーマンスは大きな武器になるが、そうした長所もゲーム開発ではそれほど大きなメリットとして働かないようだ
-
結局のところRustは万能言語ではなく、特定ドメインに特化した言語である以上、自分のプロジェクトの性格に合っているかを慎重に見極める必要がありそうだ
13件のコメント
rustは、コアレベルで完全性のあるコードを書ける開発者が減ってきて、さまざまな開発者のフォルトによって発生する問題を解決するための言語だと思います。
素早く開発するというより、
正確に開発するのにより適しているため、
ユーザーからの追加要件が頻繁に発生し、実行しなければならないプロジェクトには向いていません。
それにもかかわらず、UIライブラリが出てきてほしいと願うのは、
堅牢なコードの上で動く小さくシンプルなUIさえあれば、もっと活用しやすくなるだろうという期待があるからだと思います。
ゲーム開発をしたことがないので詳しくはわかりませんが、
そもそも最初から言語選択を誤っていたか、(開発のイテレーションが重要ならスクリプトレベルの言語を選ぶべきだった……)
あるいはシステムに対する深い理解がなかったのではないかと思います。
C++を選んでいたとしても、似たような問題にぶつかったのではないでしょうか。
その通りです。まあ、C++を選んでいたなら、Unrealという選択肢もあったでしょうが……
最近の時代では、Rust や C++ のようなネイティブ言語は、ゲームクライアント開発よりもゲームエンジンのようなミドルウェア開発のほうが適しているように思えますね。
ネイティブ言語でフルスタックWeb開発をする事例がほとんどないのと同じように。
そもそも最初からそういう用途向けに出てきた言語ですよね。C++の代替として。
この記事は、言語を批判する文章になってしまったように思います。
そうですね。もともとFirefox内部のエンジン改善のために始まったんでしたっけ?
本文は、万能薬のような感覚でRustとあまり相性の良くない分野で開発してみたら苦労した、という感じですね(笑)
100%同意します。ゲームロジックには、生産性の高い言語のほうがユースケースに合っているように思えます。
Rustを始めたばかりの段階なので、少し共感します。
Borrowing の変更でコードをあまりにも多く修正することになって、つらいですよね。
暗黙的な表現が多い Python や JavaScript と違って、Rust は明示的な傾向があるので冗長なコードになりがちですし、プログラマーに多くの選択をさせるので疲れることもあります。
それでも、他の言語にはない概念が新鮮で面白いです。
Rust Analyzer や GitHub Copilot がなかったら、早い段階で諦めていたと思います。
やはりゲーム開発はハードコアですね……
Rustでコアからフロントまで全部を書くより、
Unityやほかのゲームエンジンのように、コアはRustで書いて生産性の高い言語を組み合わせて使うのはどうでしょうか?
どうしても、かなり強く枠組みを強制する側面があるので、仕方ないでしょう。
Hacker Newsの意見
メタバースクライアント開発の経験を共有しながら、Rustの問題点を提示している。
20年の経験を持つゲーム開発者として、Rustはゲーム開発に適していないと見ている。
Rustのゲーム開発エコシステムは誇大宣伝に依存している。
BevyとUnity、Godotのゲーム開発体験を比較している。
Nim言語はRustよりゲーム開発に適している可能性がある。
Rustは特定のやり方のプログラミングを強要する独善的な言語に見える。
Allen Blomquistのツーリングデモは、開発フィードバックループが重要であることを示している。
Rustの根本的な問題は、オブジェクト指向を捨てて関数型プログラミングを好んでいること。
ゲーム開発においてRustは最悪の選択。
D言語でコーディングしながら、Cに比べてデータレイアウトの変更がはるかに容易だと気づいた。