Nach drei Jahren Spieleentwicklung mit Rust verlasse ich es
(loglog.games)- Zur Behauptung, dass alle Probleme verschwinden, sobald man sich an Rust gewöhnt hat
- Auch wenn man sich an Rust gewöhnt, verschwinden die grundlegenden Probleme nicht
- Spiele sind komplexe Zustandsmaschinen, und weil sich die Anforderungen ständig ändern, passen sie schlecht zu Rusts statischen und übermäßig restriktiven Prüfungen
- Dass der Code ständig refaktoriert werden muss, ist ein selbstverursachtes Problem
- Große Refactoring-Probleme durch den Borrow Checker
- Rust erzwingt Refactorings häufiger als andere Sprachen
- Wenn sich die Anforderungen eines Spiels häufig ändern, kämpft man mit dem Borrow Checker und muss den Code umstrukturieren
- Der Behauptung, Refactoring führe zu gutem Code, ist schwer zuzustimmen. Guter Code entsteht, indem man Ideen wiederholt ausprobiert und iteriert
- Indirektion löst nur einen Teil der Probleme und senkt die Entwicklungsproduktivität
- Wenn man zur Lösung von Borrow-Checker-Problemen Indirektion verwendet, werden Codelogik und Struktur voneinander getrennt und komplexer
- In Spielen braucht man verknüpfte Events, präzises Timing und viel Zustandsverwaltung, und Indirektion erschwert genau das
- In Rust muss selbst einfacher Code für solche Indirektion umständlich ausgeschrieben werden
- ECS(Entity-Component-System) löst das falsche Problem
- Die Vorteile von ECS sind in Wirklichkeit die Vorteile einer generational arena
- ECS wird aus verschiedenen Blickwinkeln betrachtet: dynamic composition, structure of arrays, Lösung für Rusts Borrow Checker, dynamisch erzeugte generational arenas usw.
- Ob ECS in der Spieleentwicklung aber wirklich der geeignetste Ansatz ist, bleibt fraglich. Wichtiger als Performance oder Komposition sollte die Spiellogik sein
- Verallgemeinerte Systeme führen nicht zu interessantem Gameplay
- Verwendet man zu stark verallgemeinerte Systeme, entsteht langweiliges Gameplay
- Gute Spiele entstehen durch sorgfältig entworfene Detailinteraktionen, synchronisierte VFX, wiederholtes Playtesting und Experimentieren sowie schnelles Veröffentlichen, um Feedback zu erhalten
- Rust verfolgt Werte, die für schnelles Prototyping und schnelle Iteration ungeeignet sind
- In der Spieleentwicklung sind schnelles Prototyping und schnelle Iteration entscheidend, aber Rusts Werte stehen dem entgegen
- Im Mittelpunkt der Spieleentwicklung stehen das Spiel selbst und das Spielerlebnis, nicht absturzfreier Code
- Die Wartbarkeit von Spielcode ist bei Indie-Spielen kein besonders wichtiger Wert. Entscheidend ist die Iterationsgeschwindigkeit
- Rust ist so darauf fixiert, Probleme zu vermeiden, dass es das eigentlich Wichtige aus den Augen verliert
- Procedural macros reichen bei Weitem nicht an Reflection heran
- In der Spieleentwicklung schreibt man Code für viele Bereiche: Systemcode, Gameplay-Code, UI, VFX, Audio, Tools usw.
- In Rust muss man selbst für eine einfache „Objektausgabe“ den Code direkt schreiben oder procedural macros erstellen
- Procedural macros sind im Vergleich zu deklarativen Makros viel stärker eingeschränkt und verlängern zudem die Compile-Zeit
- Reflection in C# ist sehr einfach zu verwenden und ermöglicht schnelle Entwicklung, wenn Performance nicht entscheidend ist
- Hot reloading spielt eine wichtige Rolle für höhere Iterationsgeschwindigkeit
- Hot reloading ist äußerst nützlich für Immediate-Mode-UI/Rendering, Debugging, das Tuning von Gameplay-Konstanten usw.
- Es gibt in Rust zwar
hot-lib-reloader, aber es ist nicht perfekt und erfordert Planung und Voraussicht, was kreative Nutzung einschränkt - Spieleentwickler wollen höherwertige Tools als nur das Neuladen einfacher structs
- Abstraktion ist keine Option, sondern Pflicht
- Konkretes Beispiel: Wenn je nach UI-Zustand unterschiedliches Verhalten nötig ist, muss man in Rust refaktorieren oder exzessiv klonen
- Oft ändert man den Code nicht, weil sich die Business-Logik ändert, sondern um den Compiler zufriedenzustellen
- Rust hat kein strukturelles Typsystem wie „ein Typ mit diesen Feldern“, sodass man bei Refactorings viele Stellen im Code anfassen muss
- Die GUI-Situation in Rust ist miserabel
- Bei Game-UI sind Datenbindung oder reaktive Updates nicht das Wichtigste, sondern die Möglichkeit, das Erscheinungsbild anzupassen
- Was man für Game-UI braucht, sind schöne GUI, benutzerdefinierte Sprites, Animationen, Vektorformen, Partikel, Effekte, Blitzeffekte usw.
- Derzeit gibt es keine auf Game-GUI spezialisierte Rust-Bibliothek
- Die Orphan rule sollte optional sein
- Die Orphan rule senkt die Entwicklungsproduktivität im Namen der Sicherheit stark
- In Anwendungscode, der keine Bibliothek ist, sollte man die Orphan rule deaktivieren können
- Die Compile-Zeit hat sich verbessert, ist mit proc macros aber weiterhin langsam
- Proc macros wie
serdeverlängern die Compile-Zeit erheblich - Bei incremental builds, die 20 bis 30 Sekunden oder länger dauern, wird feines Tuning sehr schwierig
- Proc macros wie
- Das Ökosystem für Rust-Spieleentwicklung wirkt übertrieben aufgeblasen
- Es gibt nicht viele Leute, die tatsächlich Spiele bauen
- Projekte mit schicker Website oder eindrucksvollem README und viel Bekanntheit sind in Wirklichkeit oft nicht so substanziell
- Wer wirklich Spiele entwickeln will, dem empfiehlt der Autor
godot-rust. Statt auf eine Pure-Rust-Lösung zu bestehen, sollte man lieber die Hilfe einer ausgereiften Engine nutzen
- Spiele laufen single-threaded, daher ist die Begründung gegen globalen Zustand fehlgeleitet
- Globalen Zustand zu verwenden ist in Rust sehr umständlich (
static mut,AtomicRefCell,Rcusw.) - Anders als Backend-Services laufen Spiele jedoch single-threaded, daher ist diese Unbequemlichkeit nicht nötig
- Dass Bevy parallele Systeme eingeführt hat, hält der Autor für einen Fehler. Das geschah zwar für bessere Performance, führt aber dazu, dass Nutzer ständig Reihenfolgen explizit angeben müssen, was die Nutzung eher unbequemer macht
- Globalen Zustand zu verwenden ist in Rust sehr umständlich (
- Dynamische Borrow-Prüfung verursacht nach Refactorings unerwartete Abstürze
- Während zweier Jahre mit
hecshat der Autor häufig Fälle gesehen, in denen überlappende Querys Abstürze auslösten - Auch bei
RefCellführen zwei.borrow_mut()an verschiedenen Stellen zu unerwarteten Abstürzen - Das Problem ist weniger schlechter Code als vielmehr, dass Rust unnötige Refactorings erzwingt
- In Spielen ist
RefCellnützlich, aber Rust macht den Einsatz unnötig schwierig
- Während zweier Jahre mit
- Context-Objekte sind nicht flexibel genug
- Weil globaler Zustand in Rust schwer zu verwenden ist, nutzt man häufig das Muster, Referenzen in einem Context-Objekt zu sammeln und weiterzureichen
- Leiht man sich dabei jedoch nur einzelne Felder aus, führt partial borrow zu Compilerfehlern
- Man sagt dann, man solle den Context aufteilen und die Struktur ändern, aber eigentlich will man an der Spiellogik arbeiten; die Struktur nur zur Zufriedenstellung des Compilers zu ändern, ist Zeitverschwendung
Vorteile von Rust
- Wenn es kompiliert, funktioniert es meist gut. Besonders nützlich ist es für CLI-Tools, Datenverarbeitung und das Schreiben von Algorithmen
- Es liefert ohne großen Aufwand gute Performance. Der Autor hat erlebt, dass es etwa 1,5- bis 2,5-mal schneller als C# ist
- Enums sind gut umgesetzt
- Dank Rust analyzer hat sich die Nutzbarkeit in der IDE stark verbessert
- Das Trait-System ist gut gestaltet. Würde nur die Orphan rule etwas gelockert, wäre es noch deutlich vielseitiger einsetzbar
Meinung von GN⁺
-
Die Einschätzung zu Rust ist insgesamt zwar eher negativ, doch da der Autor zu diesem Fazit nach vielen verschiedenen Versuchen gekommen ist, scheint es sinnvoll, die Argumente ernst zu nehmen. Vor allem der Hinweis, wie wichtig Praxistauglichkeit und Entwicklungsgeschwindigkeit in der Spieleentwicklung sind und dass Rust dort noch Schwächen hat, ist nachvollziehbar
-
Ich stimme zu, dass die von Rust angestrebte Sicherheit die Entwicklungsproduktivität mitunter stark senken kann. Beim Schreiben von Rust-Code verbringt man oft mehr Zeit damit, den Compiler zufriedenzustellen, als an der eigentlichen Logik zu arbeiten; das scheint damit zusammenzuhängen, dass Rust von Grund auf eine auf Systemprogrammierung spezialisierte Sprache ist
-
Andererseits findet Spieleentwicklung meist in einer single-threaded Umgebung statt, in der schnelles Prototyping und schnelle Iteration wichtig sind; übermäßige Sicherheitsprüfungen können dort eher schaden. Gerade aus der frühen Rust-Zeit hat man viele Berichte von gescheiterten Versuchen in der Spieleentwicklung gehört, und es wirkt, als seien die grundlegenden Probleme noch immer nicht wesentlich verbessert
-
Natürlich haben neuere Game-Engines wie Bevy die Bedienbarkeit der Spieleentwicklung mit Rust verbessert, doch an das Niveau ausgereifter Engines wie Unity oder Godot reichen sie offenbar noch lange nicht heran. Wie in diesem Beitrag beschrieben, kann es realistischer sein, Rust nicht für alles „pur“ zu verwenden, sondern zusammen mit bestehenden Engines
-
Die vom Autor genannten Stärken spürt man deutlich stärker in anderen Bereichen als bei Spielen. Gerade in Feldern wie Systemprogrammierung oder Webserver-Entwicklung sind Rusts Sicherheit und hohe Performance große Vorteile, doch in der Spieleentwicklung scheinen diese Stärken keinen so großen Mehrwert zu liefern
-
Letztlich ist Rust keine Allzwecksprache, sondern eine auf bestimmte Domänen spezialisierte Sprache; deshalb sollte man sorgfältig prüfen, ob sie zum Charakter des eigenen Projekts passt
13 Kommentare
Ich denke, Rust ist eine Sprache, die dafür gedacht ist, Probleme zu lösen, die dadurch entstehen, dass es immer weniger Entwickler gibt, die auf Core-Ebene sauberen und integren Code schreiben können,
und durch die unterschiedlichen Fehler von Entwicklern verursacht werden.
Sie eignet sich nicht dafür, schnell zu entwickeln,
sondern vielmehr dafür, präzise zu entwickeln,
und ist deshalb nicht passend für Projekte, bei denen häufig zusätzliche Anforderungen von Nutzern entstehen und die trotzdem umgesetzt werden müssen.
Trotzdem hofft man wohl auf das Erscheinen einer UI-Bibliothek,
weil die Erwartung besteht, dass schon eine kleine, simple UI auf robustem Code die Einsatzmöglichkeiten deutlich verbessern würde.
Ich habe selbst noch keine Spiele entwickelt und kenne mich daher nicht so gut aus,
aber ich habe den Eindruck, dass entweder von Anfang an die falsche Sprache gewählt wurde oder dass es an einem tiefen Verständnis des Systems fehlte.
(Wenn schnelle Entwicklungsiterationen wichtig waren, hätte man wohl eine Sprache auf Skript-Niveau wählen sollen.)
Ich denke, selbst mit C++ wäre man auf ähnliche Probleme gestoßen.
Stimmt. Wenn man C++ gewählt hätte, gäbe es natürlich noch die Option Unreal ...
Heutzutage habe ich den Eindruck, dass native Sprachen wie Rust oder C++ eher für die Entwicklung von Middleware wie Game-Engines geeignet sind als für die Entwicklung von Spiel-Clients.
So wie es ja auch kaum Beispiele für Full-Stack-Webentwicklung mit nativen Sprachen gibt.
Die Sprache wurde von Anfang an für genau diesen Zweck entwickelt – als Ersatz für C++.
Ich habe das Gefühl, der Text ist am Ende eher zu einer Kritik an der Sprache geworden.
Stimmt. Wurde das ursprünglich nicht zur Verbesserung der internen Engine von Firefox gestartet?
Der Artikel wirkt so, als hätte jemand in einem Bereich entwickelt, der nicht wirklich gut zu Rust passt, in der Hoffnung auf ein Allheilmittel, und dann ziemlich gelitten, haha
Ich stimme zu 100 % zu. Für die Spiellogik scheint eine Sprache mit hoher Produktivität besser zum Anwendungsfall zu passen.
Da ich gerade erst mit Rust anfange, kann ich das ein Stück weit nachvollziehen.
Es war ziemlich schmerzhaft, dass ich wegen Änderungen beim Borrowing so viel Code anpassen musste.
Im Gegensatz zu Python und JavaScript, die viele implizite Ausdrucksweisen haben, neigt Rust zu mehr Explizitheit, sodass manchmal ausschweifiger Code entsteht, und weil es den Programmierenden viele Entscheidungen abverlangt, kann es auch ermüdend sein.
Trotzdem sind die Konzepte, die es in anderen Sprachen nicht gibt, frisch und spannend.
Ohne Rust Analyzer oder GitHub Copilot hätte ich vermutlich schon früh aufgegeben.
Spieleentwicklung ist wirklich hardcore ...
Anstatt alles von Core bis Frontend in Rust zu schreiben,
wie wäre es, wenn man – ähnlich wie bei Unity oder anderen Game-Engines – den Core in Rust entwickelt und ihn mit einer produktiveren Sprache kombiniert?
Wahrscheinlich lässt sich das nicht vermeiden, weil es eben dazu neigt, das Gerüst ziemlich stark vorzugeben.
Hacker-News-Kommentar
Teilt Erfahrungen aus der Entwicklung eines Metaverse-Clients und nennt Probleme von Rust
Als Spieleentwickler mit 20 Jahren Erfahrung hält er Rust für ungeeignet für die Spieleentwicklung
Das Rust-Ökosystem für Spieleentwicklung lebt von Hype
Vergleicht die Erfahrungen in der Spieleentwicklung mit Bevy, Unity und Godot
Die Sprache Nim könnte für Spieleentwicklung besser geeignet sein als Rust
Rust wirkt wie eine dogmatische Sprache, die eine bestimmte Art des Programmierens erzwingt
Allen Blomquists Tooling-Demo zeigt, wie wichtig die Entwicklungs-Feedback-Schleife ist
Ein grundlegendes Problem von Rust ist die Abkehr von Objektorientierung und die Bevorzugung funktionaler Programmierung
Für Spieleentwicklung ist Rust die schlechteste Wahl
Beim Programmieren in D wurde klar, dass Änderungen am Datenlayout viel einfacher sind als in C