Rust bei einem Startup verwenden: Eine warnende Geschichte

Nov 25 2022
Rost ist genial, für bestimmte Dinge. Aber überlegen Sie es sich zweimal, bevor Sie es für ein Startup in die Hand nehmen, das sich schnell bewegen muss.

Rost ist genial, für bestimmte Dinge. Aber überlegen Sie es sich zweimal, bevor Sie es für ein Startup in die Hand nehmen, das sich schnell bewegen muss.

Alle Grafiken für diesen Beitrag wurden mit DALL-E erstellt.

Ich habe gezögert, diesen Beitrag zu schreiben, weil ich keinen heiligen Krieg um Programmiersprachen beginnen oder in ihn geraten möchte. (Nur um den Flammenköder aus dem Weg zu räumen, Visual Basic ist die beste Sprache aller Zeiten!) Aber ich wurde von einer Reihe von Leuten nach meinen Erfahrungen mit Rust gefragt und ob sie Rust für ihre Projekte verwenden sollten. Daher möchte ich einige der Vor- und Nachteile teilen, die ich bei der Verwendung von Rust in einer Startup-Umgebung sehe, in der es wirklich wichtig ist, sich schnell zu bewegen und Teams zu skalieren.

Ich möchte klarstellen, dass ich in manchen Dingen ein Fan von Rust bin . In diesem Beitrag geht es nicht darum, wie schlecht Rust als Sprache ist oder ähnliches. Worüber ich jedoch sprechen möchte, ist, dass die Verwendung von Rust mit ziemlicher Sicherheit einen nicht trivialen Produktivitätseinbruch mit sich bringen wird, der ein wichtiger Faktor sein könnte, wenn Sie versuchen, schnell voranzukommen. Wägen Sie sorgfältig ab, ob der Geschwindigkeitseffekt die Vorteile der Sprache für Ihr Unternehmen und Ihr Produkt wert ist.

Gleich vorweg sollte ich sagen, dass Rust sehr gut darin ist, wofür es entwickelt wurde , und wenn Ihr Projekt die spezifischen Vorteile von Rust benötigt (eine Systemsprache mit hoher Leistung, superstarke Typisierung, keine Notwendigkeit für Garbage Collection usw.) dann ist Rust eine gute Wahl. Aber ich denke, dass Rust oft in Situationen verwendet wird, in denen es nicht gut passt, und Teams zahlen den Preis für die Komplexität und den Overhead von Rust, ohne viel Nutzen zu ziehen.

Meine primäre Erfahrung mit Rust stammt aus der Arbeit damit für etwas mehr als 2 Jahre bei einem früheren Startup. Bei diesem Projekt handelte es sich um ein Cloud-basiertes SaaS-Produkt, das mehr oder weniger eine herkömmliche CRUD-App ist: Es handelt sich um eine Reihe von Microservices, die einen REST- und gRPC-API-Endpunkt vor einer Datenbank sowie einige andere Back- end microservices (selbst implementiert in einer Kombination aus Rust und Python). Rust wurde hauptsächlich verwendet, weil einige der Gründer des Unternehmens Rust-Experten waren. Im Laufe der Zeit haben wir das Team erheblich vergrößert (und die Anzahl der Ingenieure um fast das Zehnfache erhöht), und auch die Größe und Komplexität der Codebasis ist erheblich gewachsen.

Als das Team und die Codebasis wuchsen, hatte ich das Gefühl, dass wir im Laufe der Zeit eine immer höhere Steuer für die weitere Verwendung von Rust zahlen mussten. Die Entwicklung war manchmal schleppend, die Einführung neuer Funktionen dauerte länger als ich erwartet hatte, und das Team spürte einen echten Produktivitätseinbruch durch diese frühe Entscheidung, Rust zu verwenden. Das Umschreiben des Codes in einer anderen Sprache hätte die Entwicklung auf lange Sicht wesentlich wendiger gemacht und die Lieferzeit verkürzt, aber die Zeit für die umfangreichen Umschreibungsarbeiten zu finden, wäre äußerst schwierig gewesen. Also blieben wir irgendwie bei Rust hängen, es sei denn, wir entschieden uns, in den sauren Apfel zu beißen und einen großen Teil des Codes neu zu schreiben.

Rost soll das Beste seit geschnittenem Brot sein, also warum hat es bei uns nicht so gut funktioniert?

Rust hat eine enorme Lernkurve.

Ich habe in meiner Karriere in Dutzenden von Sprachen gearbeitet, und mit wenigen Ausnahmen in den meisten modernen, prozeduralen Sprachen (C++, Go, Python, Java usw.), die sich in ihren Grundkonzepten alle sehr ähnlich sind. Jede Sprache hat ihre Unterschiede, aber normalerweise geht es darum, ein paar Schlüsselmuster zu lernen, die sich zwischen den Sprachen unterscheiden, und dann kann man ziemlich schnell produktiv sein. Bei Rust muss man jedoch völlig neue Ideen lernen – Dinge wie Lebensdauer, Eigentum und den Kreditprüfer. Dies sind für die meisten Menschen, die in anderen gängigen Sprachen arbeiten, keine vertrauten Konzepte, und selbst für erfahrene Programmierer gibt es eine ziemlich steile Lernkurve.

Einige dieser „neuen“ Ideen sind natürlich auch in anderen Sprachen vorhanden – insbesondere in funktionalen – aber Rust bringt sie in eine „Mainstream“-Sprachumgebung und wird daher für viele Rust-Neulinge neu sein.

Obwohl sie zu den klügsten und erfahrensten Entwicklern gehörten, mit denen ich zusammengearbeitet hatte, hatten viele Leute im Team (mich eingeschlossen) Mühe, die kanonischen Methoden zu verstehen, bestimmte Dinge in Rust zu tun, wie man die oft geheimnisvollen Fehlermeldungen des Compilers grokiert oder wie man versteht, wie Schlüsselbibliotheken funktionierten (mehr dazu weiter unten). Wir begannen damit, wöchentliche „Rust lernen“-Sitzungen für das Team abzuhalten, um Wissen und Fachwissen auszutauschen. Dies alles war eine erhebliche Belastung für die Produktivität und Moral des Teams, da jeder das langsame Entwicklungstempo spürte.

Als Vergleichspunkt dafür, wie es aussieht, eine neue Sprache in einem Softwareteam einzuführen, war eines meiner Teams bei Google eines der ersten, das vollständig von C++ auf Go umgestellt hat, und es dauerte nicht länger als etwa zwei Wochen, bis das Ganze abgeschlossen war Das 15-köpfige Team konnte zum ersten Mal ganz bequem in Go programmieren. Bei Rust fühlten sich die meisten Teammitglieder selbst nach monatelangem täglichen Arbeiten in der Sprache nie vollständig kompetent. Eine Reihe von Entwicklern erzählte mir, dass es ihnen oft peinlich war, dass es länger als erwartet dauerte, bis ihre Funktionen landeten, und dass sie so viel Zeit damit verbrachten, sich um Rust zu kümmern.

Es gibt andere Möglichkeiten, die Probleme zu beheben, die Rust zu lösen versucht.

Wie oben erwähnt, war der Dienst, den wir bauten, eine ziemlich unkomplizierte CRUD-App. Die erwartete Belastung dieses Dienstes würde während der Lebensdauer dieses speziellen Systems in der Größenordnung von höchstens ein paar Abfragen pro Sekunde liegen. Der Dienst war ein Frontend für eine ziemlich aufwändige Datenverarbeitungspipeline, deren Ausführung viele Stunden dauern konnte, sodass nicht erwartet wurde, dass der Dienst selbst ein Leistungsengpass darstellt. Es gab keine besonderen Bedenken, dass eine herkömmliche Sprache wie Python Probleme haben würde, eine gute Leistung zu liefern. Es gab keine besonderen Sicherheits- oder Gleichzeitigkeitsanforderungen, die über das hinausgingen, was jeder webbasierte Dienst bewältigen muss. Der einzige Grund, warum wir Rust verwendet haben, war, dass die ursprünglichen Autoren des Systems Rust-Experten waren, nicht, weil es sich besonders gut für den Aufbau dieser Art von Diensten eignete.

Rust hat die Entscheidung getroffen, dass Sicherheit wichtiger ist als die Produktivität der Entwickler. Dies ist in vielen Situationen der richtige Kompromiss – wie das Erstellen von Code in einem Betriebssystemkern oder für speicherbeschränkte eingebettete Systeme – aber ich denke nicht, dass es in allen Fällen der richtige Kompromiss ist, insbesondere nicht in Startups, in denen Geschwindigkeit entscheidend ist. Ich bin Pragmatiker. Ich würde mein Team viel lieber Zeit in das Debuggen von gelegentlichen Speicherlecks oder Tippfehlern für Code investieren lassen, der beispielsweise in Python oder Go geschrieben wurde, als dass jeder im Team einen 4-fachen Produktivitätseinbruch erleidet, weil er eine Sprache verwendet, die diese Probleme vollständig vermeidet .

Wie ich oben erwähnt habe, hat mein Team bei Google einen Dienst komplett in Go entwickelt, der im Laufe der Zeit mehr als 800 Millionen Nutzer und etwa das Vierfache der QPS der Google-Suche in ihrer Spitze unterstützt. Ich kann an einer Hand abzählen, wie oft wir in den Jahren, in denen dieser Dienst aufgebaut und betrieben wurde, auf ein Problem gestoßen sind, das durch das Typsystem oder den Garbage Collector von Go verursacht wurde. Grundsätzlich können die Probleme, die Rust vermeiden soll, auf andere Weise gelöst werden – durch gutes Testen, gutes Linting, gutes Code-Review und gutes Monitoring. Natürlich haben nicht alle Softwareprojekte diesen Luxus, daher kann ich mir vorstellen, dass Rust in diesen anderen Situationen eine gute Wahl sein könnte.

Sie werden es schwer haben, Rust-Entwickler einzustellen.

Während meiner Zeit bei diesem Unternehmen haben wir eine Menge Leute eingestellt, aber nur etwa zwei oder drei der über 60 Mitarbeiter, die dem Engineering-Team beigetreten sind, hatten bereits Erfahrung mit Rust. Das war nicht der Versuch, Rust-Entwickler zu finden – sie sind einfach nicht da draußen. (Aus dem gleichen Grund zögerten wir, Leute einzustellen, die nur in Rust programmieren wollten, da ich denke, dass dies eine schlechte Erwartung in einem Startup-Umfeld ist, in dem Sprach- und andere Technologieentscheidungen auf agile Weise getroffen werden müssen.) Dieser Mangel Das Entwicklungstalent von Rust wird sich im Laufe der Zeit ändern, da Rust immer mehr zum Mainstream wird, aber auf Rust aufzubauen, wenn man davon ausgeht, dass man Leute einstellen kann, die es bereits wissen, erscheint riskant.

Ein weiterer sekundärer Faktor ist, dass die Verwendung von Rust mit ziemlicher Sicherheit zu einer Spaltung zwischen den Leuten im Team führen wird, die Rust kennen, und denen, die es nicht tun. Da wir für diesen Service eine „esoterische“ Programmiersprache gewählt hatten, konnten die anderen Ingenieure im Unternehmen, die ansonsten beim Erstellen von Funktionen, beim Debuggen von Produktionsproblemen usw. hilfreich gewesen wären, größtenteils nicht helfen, weil sie sich keine Gedanken machen konnten Schwänze der Rust-Codebasis. Dieser Mangel an Fungibilität im Engineering-Team kann eine echte Belastung sein, wenn Sie versuchen, schnell zu handeln und die kombinierten Stärken aller im Team zu nutzen. Meiner Erfahrung nach haben die Leute im Allgemeinen wenig Schwierigkeiten, sich zwischen Sprachen wie C++ und Python zu bewegen, aber Rust ist neu genug und komplex genug, dass es ein Hindernis für die Zusammenarbeit darstellt.

Bibliotheken und Dokumentation sind unausgereift.

Dies ist ein Problem, das (hoffentlich!) im Laufe der Zeit behoben wird, aber verglichen mit, sagen wir, Go, ist die Bibliothek und das Dokumentations-Ökosystem von Rust unglaublich unausgereift. Jetzt hatte Go den Vorteil, dass es von einem ganzen engagierten Team bei Google entwickelt und unterstützt wurde, bevor es für die Welt freigegeben wurde, also waren die Dokumente und die Bibliotheken ziemlich ausgefeilt. Im Vergleich dazu hat sich Rust lange Zeit wie ein work in progress angefühlt. Die Dokumentation für viele populäre Bibliotheken ist ziemlich spärlich, und oft muss man den Quellcode einer bestimmten Bibliothek lesen, um zu verstehen, wie man sie benutzt. Das ist schlecht.

Rust-Apologeten im Team sagten oft Dinge wie „async/await sind noch wirklich neu“ und „ja, die Dokumentation für diese Bibliothek fehlt“, aber diese Mängel wirkten sich ziemlich stark auf das Team aus. Wir haben schon früh einen großen Fehler gemacht, indem wir Actix als Web-Framework für unseren Service übernommen haben, eine Entscheidung, die zu enormen Schmerzen und Leiden führte, als wir auf Fehler und Probleme stießen, die tief in der Bibliothek vergraben waren und niemand herausfinden konnte, wie sie behoben werden konnten. (Um fair zu sein, das war vor ein paar Jahren und vielleicht haben sich die Dinge inzwischen verbessert.)

Natürlich ist diese Art von Unreife nicht wirklich spezifisch für Rust, aber es kommt einer Steuer gleich, die Ihr Team zahlen muss. Egal wie großartig die Dokumentation und Tutorials der Kernsprache sind, wenn Sie nicht herausfinden können, wie man die Bibliotheken benutzt, spielt es keine Rolle (es sei denn, Sie planen natürlich, alles von Grund auf neu zu schreiben).

Rust macht das Schruppen neuer Funktionen sehr schwierig.

Ich kenne niemanden sonst, aber zumindest für mich, wenn ich ein neues Feature baue, habe ich normalerweise nicht alle Datentypen, APIs und andere feine Details, die im Voraus ausgearbeitet wurden. Ich furze oft nur Code aus, versuche, eine grundlegende Idee zum Laufen zu bringen, und überprüfe, ob meine Annahmen darüber, wie die Dinge funktionieren sollten, mehr oder weniger richtig sind. Dies beispielsweise in Python zu tun, ist extrem einfach, da Sie schnell und locker mit Dingen wie dem Tippen spielen können und sich keine Sorgen machen müssen, wenn bestimmte Codepfade unterbrochen werden, während Sie Ihre Idee grob ausarbeiten. Sie können später zurückgehen und alles aufräumen und alle Tippfehler beheben und alle Tests schreiben.

In Rust ist diese Art von „Entwurfscodierung“ sehr schwierig, weil der Compiler sich über jede verdammte Sache beschweren kann und wird, die die Typ- und Lebensdauerprüfung nicht besteht – wofür er explizit vorgesehen ist. Dies ist absolut sinnvoll, wenn Sie Ihre endgültige, produktionsreife Implementierung erstellen müssen, aber absolut scheiße, wenn Sie versuchen, etwas zusammenzubasteln, um eine Idee zu testen oder eine grundlegende Grundlage zu schaffen. Das unimplemented!Makro ist bis zu einem gewissen Punkt hilfreich, erfordert aber immer noch, dass alles den Stack nach oben und unten überprüft, bevor Sie überhaupt kompilieren können.

Was wirklich beißt, ist, wenn Sie die Typsignatur einer lasttragenden Schnittstelle ändern müssen und Stunden damit verbringen, jeden Ort zu ändern, an dem der Typ verwendet wird, nur um zu sehen, ob Ihr anfänglicher Stich in etwas machbar ist. Und dann wiederholen Sie all diese Arbeit, wenn Sie feststellen, dass Sie sie erneut ändern müssen.

Was kann Rust gut?

Es gibt definitiv Dinge, die ich an Rust mag, und Funktionen von Rust, die ich gerne in anderen Sprachen hätte. Die matchSyntax ist großartig. Die Eigenschaften Option, Result, und Errorsind wirklich mächtig, und der ?Operator ist eine elegante Möglichkeit, mit Fehlern umzugehen. Viele dieser Ideen haben Gegenstücke in anderen Sprachen, aber Rusts Herangehensweise an sie ist besonders elegant.

Ich würde Rust unbedingt für Projekte verwenden, die ein hohes Maß an Leistung und Sicherheit erfordern und bei denen ich mir keine großen Sorgen über die Notwendigkeit gemacht habe, große Teile des Codes mit einem ganzen Team, das schnell wächst, schnell weiterzuentwickeln. Für einzelne Projekte oder sehr kleine Teams (z. B. 2–3 Personen) wäre Rust wahrscheinlich gut geeignet. Rust ist eine großartige Wahl für Dinge wie Kernel-Module, Firmware, Spiel-Engines usw., wo Leistung und Sicherheit von größter Bedeutung sind, und in Situationen, in denen es schwierig sein kann, vor dem Versand wirklich gründliche Tests durchzuführen.

Okay, jetzt, wo ich die Hälfte der Leserschaft von Hacker News ausreichend verärgert habe, ist jetzt wohl ein guter Zeitpunkt, um das Thema meines nächsten Artikels anzukündigen: Warum nanoist der Texteditor besser? Bis zum nächsten Mal!