Wie viele Entwickler interessiere ich mich schon seit geraumer Zeit für Rust. Nicht nur, weil es in so vielen Schlagzeilen in Hacker News auftaucht oder weil die Sprache einen neuartigen Ansatz für Sicherheit und Leistung verfolgt, sondern auch, weil die Leute mit einem besonderen Gefühl der Liebe und Bewunderung darüber zu sprechen scheinen. Darüber hinaus ist Rust für mich von besonderem Interesse, da es einige der gleichen Ziele und Funktionen meiner Lieblingssprache teilt: Swift. Da ich mir kürzlich die Zeit genommen habe, Rust in einigen kleinen persönlichen Projekten auszuprobieren, wollte ich mir ein wenig Zeit nehmen, um meine Eindrücke von der Sprache zu dokumentieren, insbesondere im Vergleich zu Swift.

Das große Ganze

Rust und Swift haben viele Gemeinsamkeiten: Sie sind beide kompilierte Sprachen mit leistungsstarken, modernen Typsystemen und einem Fokus auf Sicherheit. Funktionen wie algebraische Typen und die erstklassige Behandlung optionaler Werte tragen dazu bei, viele Fehlerklassen in beiden Sprachen von der Laufzeit zur Kompilierungszeit zu verschieben.

Wie unterscheiden sich diese Sprachen? Der beste Weg, den Unterschied zu charakterisieren, ist:

Swift macht es einfach, sicheren Code zu schreiben.
Rust macht es schwierig, unsicheren Code zu schreiben.

Diese beiden Aussagen mögen äquivalent klingen, aber es gibt einen wichtigen Unterschied. Beide Sprachen verfügen über Werkzeuge, um Sicherheit zu erreichen, aber sie gehen unterschiedliche Kompromisse ein, um dies zu erreichen: Swift priorisiert die Ergonomie auf Kosten der Leistung, während Rust die Leistung auf Kosten der Ergonomie priorisiert.

Der Kompromiss: Leistung vs. Ergonomie

Der größte Unterschied in der Priorität zeigt sich in der Herangehensweise dieser Sprachen an die Speicherverwaltung. Ich beginne mit Rust, weil der Ansatz der Sprache zur Speicherverwaltung eines ihrer Alleinstellungsmerkmale ist.

In Rust wird der Speicher hauptsächlich statisch verwaltet (ja, es gibt andere Modi der Speicherverwaltung wie das Referenzzählen, aber wir werden diese vorerst ignorieren). Dies bedeutet, dass der Rust-Compiler Ihr Programm analysiert und gemäß einer Reihe von Regeln entscheidet, wann Speicher zugewiesen und freigegeben werden soll.

Um Sicherheit zu gewährleisten, verwendet Rust eine neuartige Strategie namens Borrow Checking. In der Praxis funktioniert dies so, dass Sie als Programmierer jedes Mal, wenn Sie eine Variable (dh einen Verweis auf einen Speicherort) übergeben, angeben müssen, ob der Verweis veränderbar oder unveränderlich ist. Der Compiler verwendet dann eine Reihe von Regeln, um sicherzustellen, dass Sie keinen einzelnen Speicher an zwei Stellen gleichzeitig mutieren können, sodass nachgewiesen werden kann, dass Ihr Programm keine Datenrennen hat.

Dieser Ansatz hat einige sehr vorteilhafte Eigenschaften in Bezug auf Speichernutzung und Leistung. Die Ausleihprüfung kann sehr sparsam mit dem Speicher umgehen, da das Kopieren von Werten im Allgemeinen vermieden wird. Es vermeidet auch den Leistungsaufwand einer Lösung wie der Garbage Collection, da die Arbeit zur Kompilierungszeit und nicht zur Laufzeit ausgeführt wird.

Es hat jedoch einige Nachteile in Bezug auf die Benutzerfreundlichkeit. Aufgrund der Art des Eigentums in Rust gibt es einige Entwurfsmuster, die in Rust einfach nicht funktionieren. Zum Beispiel ist es nicht trivial, so etwas wie eine doppelt verknüpfte Liste oder eine globale Variable zu implementieren. Dies wird wahrscheinlich mit der Zeit intuitiver und es gibt Problemumgehungen für diese Probleme, aber Rust setzt dem Programmierer sicherlich Einschränkungen auf, die in anderen Sprachen nicht vorhanden sind.

Obwohl nicht so oft über Rust gesprochen wird, hat Swift auch eine interessante Geschichte, wenn es um die Speicherverwaltung geht.

Swift hat zwei grundlegende Arten von Variablen: Referenztypen und Werttypen. Im Allgemeinen werden Referenztypen Heap-zugewiesen und durch Referenzzählung verwaltet. Dies bedeutet, dass zur Laufzeit die Anzahl der Referenzen auf ein referenzgezähltes Objekt verfolgt wird und das Objekt freigegeben wird, wenn die Anzahl Null erreicht. Die Referenzzählung in Swift ist immer atomar: Dies bedeutet, dass jedes Mal, wenn sich eine Referenzzählung ändert, eine Synchronisation zwischen allen CPU-Threads erfolgen muss. Dies hat den Vorteil, dass die Möglichkeit ausgeschlossen wird, dass eine Referenz in einer Multithread-Anwendung fälschlicherweise freigegeben wird, verursacht jedoch erhebliche Leistungskosten, da die CPU-Synchronisation sehr teuer ist.

Rust hat auch Werkzeuge für Referenzzählung und atomare Referenzzählung, aber diese sind Opt-in und nicht die Standardeinstellung.

Werttypen hingegen werden im Allgemeinen stapelweise zugewiesen, und ihr Speicher wird statisch verwaltet. Das Verhalten von Werttypen in Swift unterscheidet sich jedoch stark davon, wie Rust mit Speicher umgeht. In Swift werden Werttypen standardmäßig kopiert, was als „Copy-on-Write“ -Verhalten bezeichnet wird, was bedeutet, dass jedes Mal, wenn ein Werttyp in eine neue Variable geschrieben oder an eine Funktion übergeben wird, eine Kopie erstellt wird.

Copy-on-Write Das Kopieren erreicht standardmäßig einige der gleichen Ziele der Ausleihprüfung: Als Programmierer müssen Sie sich im Allgemeinen keine Sorgen machen, dass sich ein Wert aufgrund eines unerwarteten Nebeneffekts an anderer Stelle im Programm auf mysteriöse Weise ändert. Es erfordert auch etwas weniger kognitive Belastung als die Ausleihprüfung, da es in Rust ganze Klassen von besitzbezogenen Kompilierungsfehlern gibt, die in Swift einfach nicht existieren. Dies hat jedoch seinen Preis: Diese zusätzlichen Kopien erfordern zusätzliche Speichernutzung und CPU-Zyklen.

In Rust ist es auch möglich, Werte zu kopieren, um Fehler bei der Fehlerprüfung zum Schweigen zu bringen, aber dies fügt visuelles Rauschen hinzu, da Kopien explizit angegeben werden müssen.

Hier haben wir also ein gutes Beispiel für die Kompromisse, die diese beiden Sprachen eingehen: Swift gibt Ihnen einige allgemeine Annahmen darüber, wie der Speicher verwaltet werden sollte, während gleichzeitig ein Sicherheitsniveau beibehalten wird. Es ist ein bisschen so, wie ein C ++ – Programmierer den Speicher gemäß den Best Practices behandelt, bevor er viel über die Optimierung nachdenkt. Dies macht es sehr einfach, Code zu springen und zu schreiben, ohne viel über Details auf niedriger Ebene nachzudenken, und auch einige grundlegende Laufzeitsicherheits- und Korrektheitsgarantien zu erreichen, die Sie in einer Sprache wie Python oder sogar Golang nicht erhalten würden. Es kommt jedoch mit einigen Leistungsklippen, von denen es leicht fällt, ohne es zu merken, bis Sie Ihr Programm ausführen. Es ist möglich, Swift-Code mit hoher Leistung zu schreiben, dies erfordert jedoch häufig eine sorgfältige Profilerstellung und Optimierung.

Rust hingegen bietet Ihnen viele spezifische Tools, um anzugeben, wie Speicher verwaltet werden soll, und legt dann einige harte Einschränkungen für die Verwendung fest, um unsicheres Verhalten zu vermeiden. Dies gibt Ihnen sehr schöne Leistungsmerkmale direkt aus der Box, aber es erfordert, dass Sie den zusätzlichen kognitiven Aufwand übernehmen, um sicherzustellen, dass alle Regeln befolgt werden.

Ich habe daraus gelernt, dass diese Sprachen zwar einige gemeinsame Ziele haben, aber grundsätzlich unterschiedliche Eigenschaften haben, die sich für unterschiedliche Anwendungsfälle eignen. Rust zum Beispiel scheint die klare Wahl für etwas wie Embedded-Entwicklung zu sein, wo eine optimale Nutzung von Speicher- und CPU-Zyklen extrem wichtig ist und wo die Code-Compile-Run-Schleife langsamer sein kann, so dass es wertvoll ist, jedes mögliche Problem zu fangen Kompilierzeit. Swift ist möglicherweise die bessere Wahl für Datenwissenschaft oder serverlose Logik, bei der die Leistung ein sekundäres Anliegen ist und es wertvoll ist, näher an der Problemdomäne zu arbeiten, ohne viele Details auf niedriger Ebene berücksichtigen zu müssen.

Auf jeden Fall werde ich sehr daran interessiert sein, diese beiden Sprachen in Zukunft zu verfolgen, und ich werde diesem Beitrag weitere Beobachtungen zum Vergleich zwischen Swift und Rust folgen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.