3 Tage Rust

In der Nachbarabteilung meiner Firma wurde festgestellt, dass dotNet Software “nicht ganz so gut performt” und deshalb musste eine schnellere Sprache her. Das wurde dann nicht C++, weil das angeblich zu kompliziert ist, sondern Rust.

Und weil in der bestellten Rust-Schulung für Mitarbeiter noch ein Platz frei war, durfte ich mich da auch hineinsetzen.


Als C und C++ Entwickler sehe ich da eine “Konkurrenz” aufkommen, deren Notwendigkeit sich mir eigentlich nicht erschließt. … aber das ist nicht verwunderlich, genau so ging es mir vor über 10 Jahren schon mit Objective-C.
Wenn man sein Handwerk beherrscht, sieht man nicht die Notwendigkeit es durch etwas anderes zu ersetzen.

Und so fiel mir intensiv auf, dass (wie immer) alles nur anders benannt ist um sich zu unterscheiden, und im Endeffekt auch “nur programmiert” wird.

Beim Längeren Nachdenken versteht man dann aber doch einige Dinge besser und kann ihren (wenn auch seltsam wirkenden) Ursprung nachvollziehen.

Die Vorteile

Formzwang

Als C/C++ Mensch liebt und hasst man die völlige Freiheit im Code gleichzeitig, denn man selbst kann sich frei entfalten, und weil das alle so machen, versteht man den Code des Nachbar nicht mehr.

Der Rust Compiler geht hier so weit, dass er Verletzungen des Codingstyles als Warnung anzeigt. Wer also
fn saySomething() schreibt, bekommt sofort mitgeteilt, dass es besser fn say_something() heißen sollte.

Und ich gebe zu, dass auch ich an mir selbst inzwischen leide, dass ich mich mal darauf festgelegt habe, in C anders zu arbeiten als in C++, damit man die Ebenen besser unterscheiden kann … aber lesbarer wird die Sache im Ganzen dadurch nicht.

Lebensdauer und Konstanten

Dass Instanzen von Variablen eine begrenzte Lebensdauer haben, ist üblich, doch Rust verfolgt jede Variable und Referenz und vermag daher dem Anwender gleich zu melden, wenn dieser auf etwas zugreift, was nicht mehr existiert.

In C++ hingegen dürfen wir sogar lokale Variablen als Referenz aus einer Funktion zurückgeben, was selten eine gute Idee ist.

Auch das transferieren einer Variable in eine andere folgt festen Spielregeln, womit es erst einmal ausgeschlossen ist, dass zwei Threads auf die gleiche Speicherzelle zugreifen dürfen. Erst mit zusätzlichem Aufwand werden solche Zugriffe möglich, was in C++ ohne Warnung oft “als Unfall” passiert.

Dass außerdem erst einmal alles “konstant” ist und explizit als “veränderbar” (mut) markiert werden muss, ist entgegen der Norm. Doch wenn man auch hier wieder an Multithreading denkt, ergeben sich damit enorme Sicherheiten, die man in anderen Sprachen schnell und oft leichtsinnig verliert.

Fehlerbehandlung

Und wer ein Fan von C-Returncodes ist und C++ Exceptions hasst, der wird Rusts enum Result<T, E> richtig lieben.
Mit dem ?-Operator wird nämlich das
if(!ok) return error; in ein Zeichen am Ende eines Aufrufs hineinkodiert. Man programmiert also den Erfolgsfall weiter und darf sich darauf verlassen, dass Rust den Fehler automatisch zurückgibt, bis er irgendwo explizit behandelt wird … und das alles ohne Exception-Overhead.

… und vieles mehr

Natürlich kann man jetzt nicht alle Vorzüge auflisten, die lernt man in jedem Rust-Tutorial. Doch die 3 genannten Punkte gefallen mir persönlich sehr.

Die andere Seite

Wenn eine Sprache aber so andersartig und seltsam ist, dass sie mit wenig vergleichbar ist, dann stellt sich einem schon die Frage, ob das gut, und vor allem ob das sinnvoll ist.

Traits vs. Klassen

Ein Mathematiker mag es anders sehen, doch ich als Embedded-Programmierer schätze das Interface- Objekt- und Vererbungsmodell der Sprachen C++, C#, und Java wirklich sehr.

Rust kennt dieses Konzept nicht, sondern lässt uns Traits für jedes neue Element implementieren. Parallel dazu werden Attribute eingesetzt um “ähnliche” Eigenschaften magisch hinzuzufügen.

Während mir in C++ eine Klasse ganz genau sagt, von wem sie Eigenschaften bekommen (geerbt) hat, kann in Rust jeder Typ im Nachhinein durch ein Trait “erweitert” werden. Übersichtlich ist das nicht.

Keine Trennung von Kernsprache und Bibliothek

Während in C++ Operatoren als Teil der Kernsprache überladen werden können, nutzt Rust (für mich willkürlich definierte) Traits.
Wer für eine Struktur das Trait std::ops::Add implementiert, der kann dann seine Instanzen mit obj1 + obj2 addieren, was ja schön und gut ist.

Doch dass dafür von der Kernsprache in einen Bibliotheksnamensraum hineingegriffen werden muss um das Operatorsymbol + zu “überladen”, ist meiner Erfahrung nach kein guter Designansatz.

Uneinheitliche Typenbestimmung

Der Rust-Compiler rät sehr geschickt herum um den Typ einer Variablen festzustellen. Man könnte diesen explizit festlegen, doch das macht man nur, wenn es sein muss.
Wann man was definieren muss scheint mir recht kompliziert definiert zu sein. Manchmal reicht ein _ als Platzhalter, dass der Compiler später den Typen einfügt, manchmal nicht.

Manchmal braucht man den “Turbofish” (mit Ähnlichkeit zu expliziten C++ Template Instanziierungen, z.B.: foo().bar::<Vec<i32>>::()), um Typen festzulegen, weil es nicht anders geht, und manchmal errät der Compiler alles ohne Typenangaben.

Selbst unser Trainer im Kurs konnte das nicht immer so genau sagen.

… und vieles mehr …

Ich will jetzt gar nicht von der hässlichen Lifetime-Syntax wie
fn foo<'a>(x: &'a i32) anfangen, kurz gesagt: Rust ist so fremdartig, dass es wenig Anknüpfpunkte (und Zusammenarbeit) mit anderen Sprachen gibt.

Fazit

Der für mich größte Nachteil von Rust ist: Es hat kein standardisiertes ABI um seine Features ins System exportieren zu können, schlimmer noch, es will auch gar keines.
Rust kann Bibliotheken und Sourcen nur in ein großes Binary zusammenkompilieren.

Das entspricht reinen statischen Bibliotheken in C++. Und das fände ich eigentlich sogar ganz gut … doch dass man eben gar keine nativen Rust DLLs oder SOs (ohne Kunstgriffe) erzeugen kann, ist eine kritische Einschränkung.

Einzige Option: eine C-API. Man kann Rust Code in C-Funktionen kapseln, damit sie von anderen Modulen benutzt werden können. Doch genau hier gehen dann alle Sicherheitsfeatures von Rust wieder verloren und man muss die sicheren Rust-Strings in Null-terminierte-C-Strings umwandeln, usw.

Als Sprache finde ich Rust allerdings sehr interessant und ich hoffe, dass C++ noch einiges daraus lernen wird.
Für mich wird es jedoch über eine “einfache” C-Anbindung vermutlich wenig Gründe geben, irgend etwas in Rust zu machen.

Dafür habe ich in C/C++ einfach schon 15 Jahre Vorsprung.

Außerdem … inzwischen gibt es ja mit ZIG die nächste Programmiersprache, die besser und performanter als Rust sein soll. Und wenn immer alle von vorne anfangen “ihre eigene” Sprache neu zu entwickeln, gibt es auf lange Sicht auch keinen Fortschritt.

📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!