Hier bei trendig versuchen wir, mit den neuesten Trends und Technologien in unserem Bereich immer aktuell zu sein.
Meine bisherigen Projekte waren meist in GO, C#, TypeScript oder Python geschrieben und ich wollte etwas Freizeit investieren, um eine neue Sprache zu lernen. Nur um zu sehen, was sie zu bieten hat und ob sie für zukünftige Projekte gut geeignet ist.
Ich hatte die Sprache Rust schon seit geraumer Zeit im Blick, bin aber nie dazu gekommen, mit wirklich mit ihr zu beschäftigen. Schließlich ergriff ich die Gelegenheit und portierte ein altes Programm, das ich in GO geschrieben hatte, um ein gutes Verständnis über die Sprache, die Infrastruktur und das Ökosystem von Standard- und externen Bibliotheken zu bekommen. Die Sprache schien auch stabil und reif genug zu sein, um zukünftige Projekte in ihr zu schreiben.
Nach der Lektüre des aktuellen Entwurfs von „The Rust Programming Language“ entschied ich mich, ein altes CLI-Programm, das in GO geschrieben wurde, nach Rust zu portieren. Es ist ein Media-Tool zur Dekodierung von NWA-Dateien - das sind Audio- / Musik-Dateien, die in einer alten Game Engine von Key verwendet werden, in WAV-Dateien. Dieses kleine Programm ist ein geeignetes Projekt für die Portierung, da es viele Dinge hat, die viele Aspekte einer Programmiersprache testen werden:
Eine Bibliothek und ausführbare Komponenten zu pflegen.
Eine CLI-Schnittstelle zu pflegen.
Einfache IO-Bedienung (lesen / schreiben von Dateien).
Verwendung von Low-Level Bit- und Byte-Operationen mit klar definierter Datengröße und Byte-Reihenfolge.
Parallelisierung des Codes.
Nichts ausgefallenes und ganz einfach, aber es hat viele Bausteine, um eine neue Sprache in vielen Aspekten zu testen.
Ich brauchte ca. 5 Tage, um mein altes GO-Programm nach Rust zu portieren, ohne vorher Erfahrungen mit der Sprache gesammelt zu haben. 30% der Zeit hatte ich ein Problem mit dem io::BufReader behoben, was darauf zurückzuführen ist, dass ich falsche Annahmen über das Verhalten von io::BufReader getroffen habe.
Am Ende habe ich mein kleines Projekt erfolgreich abgeschlossen und es lief korrekt und sogar schneller als meine GO-Implementierung (die nicht wirklich gut optimiert und deren Codebasis ziemlich alt ist).
Das Endresultat kannst Du Dir unter Gitlab ansehen.
Technisch gesehen fühlten sich folgende Punkte für Rust gut an:
Die Installation von Rust und die Aktualisierung von rustup sind großartige Erlebnisse. Ich mag es nicht, dass viele Entwicklungswerkzeuge Rust Nightly Builds benötigen. GO hatte das gleiche Problem auch, vor vielen Jahren, bevor sie stabil wurde. Ich hoffe, dass diese Notwendigkeit bald verschwinden wird, da es die Entwicklung mit vielen Entwicklern und die konsistente Haltung des Build-Prozesses über viele Systeme hinweg nur erschwert hat.
Ich fand Cargo als ein großartiges Tool durch viele gute Erfahrungen mit dem Go-Befehl in GO in der Vergangenheit.
Abhängigkeitsmanagement fühlte sich gut an. Ich hatte in keinem meiner GO-Projekte Probleme mit dem losen Abhängigkeitsmanagement (mit Hilfe des Vendor-Ordners und godep), hatte aber viele unangenehme Erfahrungen mit NPM. Cargo scheint ein gutes Tool zwischen diesen beiden Extremen zu sein.
Der Compiler ist klasse. Fehler und Warnmeldungen sind hilfreich und ich hatte nie das Gefühl, gegen den Compiler zu arbeiten.
Es gibt etwas netten syntaktischen Zucker an einigen Stellen. Insbesondere die Fehlerbehandlungskonvention mit dem „Result Enum“ und dem ?-Operator.
Und diese Punkte fühlten sich seltsam an:
Ich mag nicht die Art und Weise, wie Rust den Quellcode und die Testdateien strukturiert. Von meinen Erfahrungen mit GO's Modulen und der _test.go Dateikonvention ausgehend, fühlte sich die Art und Weise, wie Rust Module und Tests definiert, sehr wortreich (sic!) und kompliziert (mod.rs Dateien und Verwendung des Keywords „mod“) an. Es ist aber immer noch viel besser als C / C++. Am Ende ist es nur Konvention, also hat es mich später nicht weiter gestört.
Fehler haben keinen Aufrufstapel, der mit ihnen verbunden ist. Nachdem ich eine Menge Backend-API-Code geschrieben habe, wo ein Aufrufstapel eines Fehlers im Log wirklich bei der Behebung von Problemen und Bugs hilft, gefällt mir die Idee, einen Aufrufstapel an einen Fehler anzuhängen. GO hat das gleiche Problem, aber es wird hauptsächlich durch die Verwendung des pkg/errors Moduls behoben (was irgendwie Standard für alle meine Projekte ist). Nach einiger Suche konnte ich keinen netten und einfach zu bedienenden Drop-In-Ersatz für dieses Problem finden. Wenn Du also eine clevere Lösung für dieses Problem kennst, kannst Du gerne einen Kommentar abgeben.
Lebensdauer: Sie sind seltsam. Sie fordern mein Gehirn und meinen gesunden Menschenverstand so sehr heraus, dass ich es ablehne, Referenzen in meinen Strukturen zu verwenden. Besonders wenn man von GO kommt, fühlt sich ihr Gebrauch fremd an. Ich habe Generika und Eigenschaften anstelle von Lebensdauern verwendet. Das Ausleihen auf der anderen Seite fühlte sich gut an und der Compiler hat einen tollen Job gemacht, indem er mir dabei geholfen hat.
Die Standardbibliothek ist ressourcenarm und fühlte sich an manchen Stellen inkonsistent an. Beispielsweise musste ich externe “Crates” verwenden, um Funktionen zum endian-sensitiven Lesen von Daten zu erhalten, indem ich eine CLI-Schnittstelle geschrieben und eine Green-Thread-ähnliche Parallelisierung meiner Arbeit ermöglicht habe. Letztendlich war das kein Problem, denn das Ökosystem um Rust bietet gute Implementierungen für diese Probleme, aber die Standardbibliotheken, die in einer Programmiersprache wie GO enthalten sind, bieten viel mehr Funktionalität. Die größte Inkonsistenz, die ich hatte, war die Notwendigkeit, ein &'a [u8] anstelle eines Vec<u8> zu verwenden, wenn ich einen Reader auf Byte-Daten verwende. Vec<u8> bietet eine Eigenschaft für einen Writer, aber nicht für einen Leser... Ich bin sicher, dass es gute Gründe dafür gibt. Abber für mich, der die Sprache zum ersten Mal benutzte, fühlte es sich einfach inkonsistent an.
Definieren von Fehlern fühlte sich wortreich an.
Überall Makros. Ich habe in der Vergangenheit keine guten Erfahrungen mit ihnen gemacht. Ich hoffe, Rust wird mir das Gegenteil beweisen.
Shadowing. Ich vermute, dass es eine nette Funktion ist, aber ich bin nicht daran interessiert, die bösen Bugs zu debuggen, die sie in der Wildnis produzieren können. Nochmals hoffe ich, dass Rust mir das Gegenteil beweisen wird.
Nach Abschluss meines Projektes hatte ich die folgenden Eindrücke über die Verwendbarkeit von Rust in einer Unternehmensumgebung:
Das Einbinden neuer Entwickler in Rust und ein Rust-Projekt wird viel länger dauern als z.B. ein GO-basiertes Projekt. Ohne GC, mit einem Entlehnkonzept, Statement und Ausdrücken, einem Match-Keyword, Makros und Generika ist Rust viel komplizierter als z.B. GO. Wir brauchen für einen neuen Entwickler drei Tage, um GO kennenzulernen, bis er oder sie mit production-ready Code zum Projekt beitragen kann. Ich schätze, dass das Einbinden eines neuen Entwicklers in Rust zwischen 6 und 10 Tagen dauern wird.
Das Ökosystem von Rust in der Webserver- / Backendumgebung scheint nicht so ausgereift und kampferprobt zu sein wie seine GO-basierten Pendants. Dies ist nur eine erste Vermutung, aber nachdem ich mir die derzeit verfügbare Standardbibliothek und externe “crates” für Rust in diesen Bereichen angesehen habe, muss ich einen kleinen POC machen, um mehr Vertrauen in diesem Bereich zu gewinnen.
Nachdem ich das alles gesagt habe, denke ich, dass Rust eine schöne Sprache ist, die definitiv eine gute Nische füllt. Ich habe sie noch nicht für ein Backend-basiertes Projekt verwendet (wie eine REST-API), aber ich glaube, dass sie etablierte Sprachen für diesen Anwendungsfall und vor allem GO in meinen Anwendungsfällen nicht entthronen wird. Aber ich hoffe, dass ich Rust trotzdem in einem zukünftigen Projekt einsetzen kann, da ich meine Annahmen testen und mehr Erfahrung damit sammeln möchte, wie gut Rust für die Backend-Seite von Web-Operationen geeignet ist.
Übersetzt aus dem Englischen »A quick dive into Rust«.