Was gut lief
- Der Rewrite erfolgte in kleinen Schritten (inkrementell, stop-and-go), funktionierte gut, und der neue Code wurde leichter zu lesen und zu verstehen
- Durch den vollständigen Überblick über den gesamten Code ergaben sich Möglichkeiten zur Performance-Optimierung
- Etwa 1/3 bis 1/2 des ungenutzten Codes wurde entfernt. Moderne Programmiersprachen wie Rust oder Go finden Dead Code besser und weisen Entwickler darauf hin
- Keine Sorgen mehr über Out-of-Bounds-Zugriffe oder Overflow/Underflow
- Das eingebaute Test-Framework ist sehr nützlich
- Ich freue mich, die CMake-Dateien losgeworden zu sein
Was nicht gut lief
Undefined Behavior muss weiterhin aufgespürt werden
- Beim schrittweisen Rewrite von C/C++ nach Rust mussten viele rohe Pointer und
unsafe{}-Blöcke verwendet werden
- Die Rust-Regeln gelten auch innerhalb von
unsafe, aber da der Compiler sie nicht prüft, kann Undefined Behavior leicht entstehen
- Innerhalb von
unsafe lässt sich die Regel „mehrere schreibgeschützte Pointer XOR ein veränderbarer Pointer“ leicht verletzen
- Miri erwies sich dabei als Rettung
Miri funktioniert nicht immer, und Valgrind wird weiterhin benötigt
- Wenn Bibliotheken verwendet werden, die Teile in C oder Assembler enthalten, etwa Kryptografie-Bibliotheken, funktioniert Miri nicht
- Es gibt viel
unsafe-Code, der von Miri nicht geprüft wird
- Einige Tests mussten unter
valgrind ausgeführt werden
Memory Leaks müssen weiterhin aufgespürt werden
- Ein typisches Muster bei C-APIs ist, dass in
MYLIB_init() Speicher alloziert und in MYLIB_release() wieder freigegeben wird, aber man vergisst leicht, MYLIB_release aufzurufen
- Rust-Entwickler möchten dafür Wrapper-Objekte mit RAII bauen, aber in Tests, die die C-API verwenden, lässt sich das nicht nutzen
- In komplexer Logik ist es schwierig, Cleanup-Funktionen immer aufzurufen. In C löst man das mit
goto, aber Rust unterstützt das nicht
- Mit dem
defer-Crate ließ sich das lösen, aber dem Borrow Checker gefällt das nicht
Cross-Compilation funktioniert nicht immer
- Wie bei Miri funktioniert
cargo build --target=... nicht ohne Weiteres, wenn Bibliotheken Teile enthalten, die in C oder Assembler implementiert sind
Cbindgen funktioniert nicht immer
- Cbindgen wird häufig verwendet, um C-Header aus einer Rust-Codebasis zu erzeugen, hat aber Grenzen oder Bugs
Instabile ABI
- Nützliche Standardbibliothekstypen wie
Option haben keine stabile ABI, sodass sie mit der Annotation repr(C) manuell nachgebaut werden müssen
Fehlende Unterstützung für benutzerdefinierte Memory Allocators
- Viele C-Bibliotheken erlauben es dem Nutzer, zur Laufzeit einen Allocator bereitzustellen. In Rust kann nur zur Compile-Zeit ein globaler Allocator gewählt werden
- Probleme bei der Ressourcenbereinigung lassen sich mit Arena-Allocators lösen, aber das ist in Rust nicht idiomatisch und nicht in die Standardbibliothek integriert
Komplexität
- Für FFI müssen Dinge wie
UnsafeCell, RefCell, MaybeUninit und Pin verwendet werden, was die Komplexität erhöht
- Schon pures Rust ist komplex, und mit einer zusätzlichen FFI-Schicht wird es zum Monster
- Wegen der Rust-Komplexität gab es auch Entwickler, die die Arbeit an dieser Codebasis abgelehnt haben
Fazit
- Insgesamt bin ich mit dem Rust-Rewrite zufrieden, war in einigen Bereichen aber enttäuscht, und er hat deutlich mehr Aufwand gekostet als erwartet
- Rust mit viel Interaktion mit C fühlt sich wie eine völlig andere Sprache an als pures Rust. Es gibt viel Reibung und viele Fallstricke. Viele Probleme von C++, die Rust angeblich löst, sind in Wahrheit überhaupt nicht gelöst
- Großer Dank an die Entwickler von Rust, Miri und cbindgen. Sie haben Erstaunliches geleistet. Trotzdem wirken Sprache und Werkzeuge für starke Nutzung von C FFI unreif, fast wie vor Version 1.0
- Wenn sich die Ergonomics von
unsafe, die Standardbibliothek, die Dokumentation, die Tools und die instabile ABI künftig verbessern, könnte das eine deutlich angenehmere Erfahrung werden
- Offenbar haben auch Microsoft und Google all das gespürt und investieren deshalb tatsächlich Geld in diesen Bereich
- Wer Rust noch nicht kennt, sollte im ersten Projekt besser pures Rust verwenden und Abstand von FFI-Themen halten
- Anfangs wurde erwogen, für diesen Rewrite Zig oder Odin zu verwenden, aber eine Sprache vor Version 1.0 sollte nicht in einer produktiven Unternehmens-Codebasis eingesetzt werden. Inzwischen stellt sich die Frage, ob die Erfahrung wirklich schlechter gewesen wäre als mit Rust. Vermutlich passt das Rust-Modell einfach nicht gut zum C-Modell (oder C++-Modell), weshalb die Reibung bei gemeinsamer Nutzung zu groß ist
- Wenn künftig wieder eine ähnliche Aufgabe ansteht, würde Zig ernsthaft in Betracht gezogen werden. Immer wenn jemand sagt: „Schreibt es doch einfach in Rust neu“, sollte man ihm diesen Artikel zeigen und fragen, ob er seine Meinung geändert hat
12 Kommentare
Auch wenn Zig noch vor v1 steht, ist es brauchbarer als gedacht, weil sich viele C-Bibliotheken nutzen lassen. Wenn man einem laufenden C-basierten Projekt etwas hinzufügen will, kann Zig dafür besser geeignet sein als Rust.
Als ich mir Rust ansah, bekam ich in dem Moment, als ich das Keyword
unsafesah, ein mulmiges Gefühl ...Ich denke allerdings nicht, dass Rust die chronischen Probleme von C++ lösen kann. Dabei geht es weniger um die Syntax als um die praktische Perspektive.
Die Gründe dafür sind:
Bereits sehr viele Produktivsysteme verwenden C/C++. Und sie laufen stabil und zuverlässig. Die meisten versuchen auch nicht unbedingt, das nach Rust zu portieren.
Die Hardware ist von vornherein nicht unter der Annahme von Reference Counting gebaut. Viele Einsatzfälle für C/C++ dienen dazu, Hardware, Betriebssysteme, Treiber und die Binärebene mit hoher Geschwindigkeit zu steuern. Um Rust zu unterstützen, müssen Low-Level-Entwickler am Ende die Ressourcen-Lebenszyklen mit
unsafeselbst verwalten, und auch das ist mit hohen Kosten verbunden.Ich halte die Erfahrung des Autors für wichtiger als der immanente Wert der Sprache oder theoretische Diskussionen.
Mir scheint, dass das Niveau des Ressourcenmanagements in Bereichen, in denen tatsächlich eine Sprache auf C/C++-Niveau benötigt wird, etwas ist, das sich nur schwer durch Rust ersetzen lässt.
Auch dieser Text stürzt sich auf Rust, ohne es richtig verstanden zu haben.
Wenn man sich den Inhalt ansieht, scheint es sich um eine Bibliothek zu handeln, die häufig mit der Außenwelt von Rust kommunizieren muss; ab diesem Punkt kann es gar nicht anders, als schmutzig zu werden ... Schon bei nativen Sprachen gibt es von vornherein keine, die nicht schmutzig wäre, und Rust kapselt das auf Sprachebene nur sicher ab, deshalb gehen viele der Vorteile verloren, je mehr Berührungspunkte es mit Bereichen außerhalb der Sprache gibt.
Das stimmt bis zu einem gewissen Grad, aber ich denke, im Entwicklungsumfeld des Originaltexts konnten diese Probleme gar nicht gelöst werden; das Problem war, dass man Rust wie ein Allheilmittel betrachtet und so an die Sache herangegangen ist.
Für mich klang das so, als sei es sinnlos, C/C++ schrittweise auf Rust umzustellen, weil man dabei zwangsläufig
unsafeverwenden müsse. Anstatt Rust schrittweise einzuführen, würde man eher Zig wählen. An welcher Stelle im Haupttext steht denn, dass es sich um eine Bibliothek handelt, die häufig mit Komponenten außerhalb von Rust kommunizieren muss?Dass FFI verwendet wird, bedeutet ja gerade, dass mit der Außenwelt von Rust kommuniziert wird.
Und wenn man sich den Inhalt des Haupttextes ansieht, wirkt es nicht so, als bliebe es beim einfachen Austausch irgendeines Zustands oder einiger weniger Daten, sondern als würden Innen- und Außenwelt auf komplexe Weise miteinander interagieren.
Muss man nicht zwangsläufig FFI einsetzen, wenn man eine in C geschriebene Bibliothek schrittweise durch Rust ersetzt? Man würde dann kleine Teile des Programms auf Rust umstellen und den verbleibenden C-Teil über FFI abwickeln müssen — meinten Sie mit „Kommunikation mit außen“ vielleicht solche Arbeiten? Wenn ja, dann finde ich es nachvollziehbar, dass der Autor dem Thema Rust gegenüber skeptisch wird. Solange man nicht den gesamten Code auf einmal austauscht, hat man schließlich kaum Vorteile von Rust, weshalb dann wohl Zig empfohlen wird.
^-^
Da explizit
unsafee Stellen im Quellcode markiert werden, hatte ich erwartet, dass es nützlich wäre, weil sich der gesamte Einflussbereich der FFI identifizieren lässt, solange man nicht schon ab dem Programmeinstiegunsafe-Blöcke verwendet. Beim Autor scheint das aber nicht besonders angekommen zu sein.Spätestens ab dem Zeitpunkt, an dem FFI verwendet wurde, war ein sicheres Design praktisch ausgeschlossen.
Ja, genau.
Stimmt, sie schreiben ganz selbstbewusst, dass alles mit
unsafezugepflastert ist, und dann ist es trotzdem nicht gelöst worden ...