Warum habe ich mich für Rust entschieden? Die Speicherverwaltung von Rust ist mit einer steilen Lernkurve verbunden. Sein Ökosystem ist nicht so weit entwickelt wie das einiger anderer Sprachen. Dennoch ist Rust sehr leistungsfähig, verfügt über eine der besten Unterstützungen für Web-Assembly und ist dennoch eine ausdrucksstarke Sprache. Lassen Sie uns diese Eigenschaften im Kontext eines konkreten Anwendungsfalls betrachten.
Eine dezentralisierte Computer-Infrastruktur
Mein Anwendungsfall ist der Aufbau einer dezentralen, interessenbasierten Computing-Infrastruktur. Das Ziel ist es, die Hardware, die sich im Besitz von Benutzern befindet, maximal für die Interessen ihrer Besitzer zu nutzen. Die Geräte hosten gemeinsam einen globalen Adressraum für typisierte Variablen. Peers können die Variablen ihres Interesses abonnieren und erhalten sowohl den bestehenden Status als auch zukünftige Aktualisierungen. Peers können Daten jederzeit ändern und Änderungen an abonnierte Peers replizieren, wenn sie online sind. Schließlich können Peers Funktionen definieren, die auf Änderungen reagieren und die Ergebnisse als neue Variablen speichern. Die Architektur des Netzwerk-Overlay- und Replikationsmechanismus ähnelt der von IPFS PubSub. Es beherbergt konfliktfreie replizierte Datentypen (CRDTs) in einem halbstrukturierten Peer-to-Peer-Netzwerk. CRDTs nutzen Monotonie, was voraussetzt, dass Aktualisierungen die Datenstruktur aufblähen. Das Hinzufügen von Elementen ist also erlaubt. Das Entfernen von Elementen erfolgt durch das Erhöhen hostspezifischer Versionen. Das Zusammenführen monotoner Aktualisierungen führt zu konsistenten Ergebnissen über alle Peers hinweg und macht die gemeinsame Nutzung von Daten in jedem Maßstab sicher. Monotone Aktualisierungen können auch transformiert, gefiltert und aggregiert werden. Lasp ist ein Beispiel, das solche Operationen für Set CRDTs ermöglicht. Das Ziel ist es, eine breite Palette von Datentypen zu unterstützen und die Möglichkeit zu bieten, nützliche Berechnungen für diese Typen auszudrücken. Ich habe meine Experimente bisher in Scala durchgeführt, habe aber Rust gewählt, um das System als Ganzes zu bauen. Hier ist der Grund dafür:Die Stärken des Rosts
Stärke 1: Leistung auf Systemebene
Die Leistung von Rust entspricht in etwa dem Niveau von C und C++. Abstraktionen zum Nulltarif Komposition ohne Effizienzverluste ermöglichen. Eine von Hand optimierte Lösung wird nicht besser sein als eine, die Abstraktionen zusammensetzt. Die Beibehaltung der Effizienz ist ein großer Vorteil für Geräte mit eingeschränktem Akku und CPU. Ein Großteil dieser Effizienz wird durch die deterministische Speicherverwaltung von Rusts erreicht. Dies ist nicht nur ein Vorteil für Geräte mit Speicherbeschränkungen. Vorhersagbare Zuweisung und Freigabe bedeuten vorhersagbare Leistung. Dies ist ein großer Vorteil für kollaborative Echtzeitanwendungen. Um an die Autorität zu appellieren: Google wählte Rust für die Entwicklung seines kollaborativen Editors Xi. Sie fragen sich vielleicht, warum wir auf Rechnern mit solch eingeschränkter Hardware laufen müssen? Die Wahrheit ist, dass die Verbraucher zunehmend mobile Geräte verwenden. Ihre instabile Konnektivität ist eine Herausforderung für die Gesamtqualität eines dezentralen Dienstes. Diese Instabilität kann überwunden werden, allerdings auf Kosten einer höheren Hardwareauslastung. Wir wissen, dass das Gegenteil wünschenswert ist. Im Idealfall wären wir in der Lage, den Dienst zu unterbrechen, um den Akku zu schonen. Eine effiziente Sprache hilft bei der Reduzierung des Overheads, reicht aber allein nicht aus. Effizienz hilft dabei, mehr Gerätetypen in das Netzwerk einzubeziehen. Modems und Router haben eine begrenzte Hardware, aber eine stabile Konnektivität. Dadurch sind sie für die Overlay-Verwaltung gut geeignet. Außerdem lässt sich mit ihnen das NAT-Traversal-Problem umgehen, das das Design von Peer-to-Peer-Systemen erschwert. Ein weiterer interessanter Gerätetyp, den wir einbeziehen können, ist Network Attached Storage (NAS). Mobile Geräte können die Aufgaben des Netzwerk-Overlay und der Replikation auf NASs verlagern, wenn diese verfügbar sind. Wenn Sie sich für Rust entscheiden, kann die Infrastruktur auf einer großen Anzahl verschiedener Geräte laufen.Stärke 2: Sicherheit durch Isolierung von Systemkomponenten
Der Wunsch nach völlig offenen Systemen steht in starkem Gegensatz zur Sicherheit. Die Wahl der Sprache allein löst das Problem nicht, kann aber Teil einer größeren Sicherheitsstrategie sein. Man kann einige Arten von Risiken für eine P2P-Anwendung unterscheiden, die einen gemeinsam genutzten, veränderlichen Zustand hostet. Meine drei wichtigsten wären: 1) das System in epidemischer Weise zu übernehmen 2) sich als der/die Besitzer einer Maschine auszugeben 3) destruktive Manipulation von lokal gespeicherten Daten Diese Risiken werden durch die Isolierung von Systemkomponenten gemildert. Jede Systemkomponente kann in einer eigenen Sandbox laufen, die durch sicherheitsrelevante Schnittstellen getrennt ist. Von allen verfügbaren Sandboxen sind diejenigen, die Skripte in Webbrowsern ausführen, am ausgereiftesten. Ihre weite Verbreitung bietet einen maximalen Anreiz zur Ausnutzung. Schließlich werden Sandboxen, die Web Assembly (WASM) wird die gleichen Isolationsgarantien bieten. Das Fehlen einer komplexen Laufzeitumgebung in Rust erleichtert die Kompilierung nach WASM. Seine Unterstützung ist bereits in der höheres Niveau unter den Sprachen. Wir können davon ausgehen, dass sich dieser Trend fortsetzen wird, da Mozilla der Hauptakteur hinter Rust ist.Stärke 3: Die Macht der Abstraktion
Wie bereits erwähnt, können Peers inkrementelle Aktualisierungen von Variablen abonnieren. Solche Änderungen können transformiert, gefiltert, aggregiert und dann als neue Variablen gespeichert werden. Die Sicherstellung ihrer Monotonie ist jedoch nicht trivial. Glücklicherweise ist es möglich, einen Großteil dieser Komplexität hinter Datenflussoperationen zu verbergen. Datenflussmodelle, die Monotonie und Inkrementalität verwenden, wurden in den letzten Jahren ausgiebig erforscht. Lasp, Differentieller Datenfluss (entwickelt in Rust!) und Strukturiertes Streaming demonstrieren alle praktische Datenfluss-APIs. Die funktionale Programmierung ist gut geeignet, um Datenfluss-Sprachen einzubetten. Sie ermöglicht sowohl einen deklarativen als auch einen imperativen Programmierstil. Ein deklarativer Stil kommt der Wartungsfreundlichkeit zugute, da die interne Komplexität verborgen bleibt. Ein imperativer Stil bietet die Kontrolle, um komplexe Kompositionen performant zu halten. Rust ist nicht die fortschrittlichste funktionale Programmiersprache. Aber sie bietet Typparameter, Typklassen und Funktionen höherer Ordnung. Dies sind die primären Mechanismen, die ich bei meinen Scala-Experimenten verwendet habe, so dass ich erwarte, dass Rust ausreicht.Die Risiken bei der Wahl von Rust
Bisher haben wir die Stärken von Rust betrachtet, wie sieht es mit seinen Schwächen aus? Sind sie ein Problem, und wenn ja, sind sie akzeptabel? Ich glaube nicht, dass Rust große Schwächen hat, aber es gibt zwei große Risiken. Diese sind so groß, dass ich als Berater die Sprache vielen meiner Kunden nicht empfehlen würde. Schauen wir mal, wie sie auf meinen Anwendungsfall zutreffen!Risiko 1: Rust hat eine steile Lernkurve
Das wichtigste Verkaufsargument von Rust, die typisierte Speicherverwaltung, ist auch eine wichtige Quelle der Komplexität. Rust führt neue Konzepte ein, an die man sich erst einmal gewöhnen muss. Sie werden die Ausführung Ihres Codes verschieben müssen, weil Rust seiner Speichersicherheit noch nicht vertraut. Das kann eine Quelle der Frustration sein und ein Gefühl der Unproduktivität hinterlassen. Es kann sich anfühlen, als würden Sie die ganze Zeit über die Speicherverwaltung nachdenken, anstatt nur auf einigen wenigen kritischen Pfaden. Diese Komplexität ist jedoch nicht zufällig. Sie ist eine ausdrückliche Designentscheidung, die dazu beiträgt, Fehler frühzeitig zu vermeiden. Fehler wie Segmentierungsfehler oder Garbage Collection Stalls lassen sich nur schwer durch Tests verhindern. Sie sind besonders frustrierend zu diagnostizieren und zu reproduzieren, wenn sie in der Produktion auftreten. Mein Anwendungsfall ist aus Sicht der kontinuierlichen Bereitstellung eine Herausforderung, da ich nur wenig Kontrolle darüber habe, wann Aktualisierungen stattfinden. Ich kann mir keinen Test-on-production-Ansatz leisten, da es keine Möglichkeit zur Wiederherstellung gibt. Die Vermeidung von Fehlern zu einem frühen Zeitpunkt ist ein Muss, daher macht sich die komplexe Speicherverwaltung von Rust in meinem Fall bezahlt.Risiko 2: Rust hat ein kleines Ökosystem
Im Vergleich zu einigen beliebten Sprachen wie C# und Java ist das Ökosystem von Rust winzig. Obwohl Rust drei Jahre in Folge bei Stackoverflow als "beliebteste Sprache" ausgezeichnet wurde, ist die Community immer noch klein. Außerdem wird Rust außer von Mozilla selbst kaum von Unternehmen unterstützt. Eine kleinere Gemeinschaft ist nicht unbedingt schlecht, denn sie fördert die Konzentration auf wenige Initiativen. In größeren Ökosystemen wird sehr viel experimentiert. Das birgt die Gefahr, dass Lösungen gewählt werden, die die Unterstützung der Gemeinschaft verlieren. Aber dieses Experimentieren steigert auch die Qualität der längerfristigen Initiativen. Aus diesem Grund gibt es nur wenige produktionsreife Rusts-Bibliotheken. Das oben Gesagte deutet auf die folgende allgemeine Schlussfolgerung hin. Sie können sich für Rust entscheiden, wenn Ihr Anwendungsfall entweder einfach ist (insbesondere aus Sicht der Integration) oder wenn er exotisch ist. Im ersten Fall würden Frameworks nur zu einer Aufblähung der Funktionen führen. Im zweiten Fall ist eine Community wahrscheinlich ohnehin keine Hilfe. Der Umkehrschluss wäre: Wählen Sie Rust nicht, wenn Sie etwas Komplexes machen wollen, das bereits jeder macht. Erwarten Sie keine optimale Produktivität, wenn Sie die nächste Multi-Cloud-Microservices-Architektur in Rust schreiben. Mein Anwendungsfall wird als exotisch eingestuft. P2P-Systeme sind selten und ausgereifte Werkzeuge sind unabhängig von der Sprache so gut wie nicht vorhanden. Für meinen Anwendungsfall benötige ich Unterstützung für Protokolle (TCP, HTTP und UDP) und Serialisierung (JSON, Protobuf). Rust verfügt über ausreichende Unterstützung für diese Funktionen.Fazit
Der Aufbau eines dezentralen, veränderbaren Datenspeichers ist mit großen Herausforderungen verbunden. Die Wahl der richtigen Sprache hilft dabei, den Problemraum erheblich zu reduzieren. Die Systemleistung von Rust ermöglicht die Einbindung einer breiten Palette von Geräten mit unterschiedlichen Stärken. Die WASM-Kompilierung von Rusts bietet verbesserte Sicherheit durch die Isolierung von Komponenten. Die Ausdruckskraft von Rusts hilft mir nicht nur dabei, die Komplexität dieser Domäne zu bewältigen, sondern ermöglicht auch bequeme APIs, um mit den Daten zu arbeiten. Die allgemeinen Risiken von Rust treffen auf meinen Anwendungsfall nicht zu. Die nicht-triviale Speicherverwaltung von Rust zahlt sich aus, wenn Sie in einer hochgradig konkurrierenden Umgebung arbeiten. Das kleinere Ökosystem von Rusts ist kaum ein Problem, wenn die Kommunikation mit anderen Peers Ihr Hauptanliegen ist. Ich hoffe, dieser Blog hat Ihnen gefallen. Wenn ja, sollten Sie an unserem Rust Meetup am 18. September teilnehmen.Verfasst von
Merlijn Boogerd
Contact
Let’s discuss how we can support your journey.