Rust-Comic

 

Memory Ownership in Rust

Was ist das Besondere daran, wie die Programmiersprache Rust für Speichersicherheit sorgt, gerade im Gegensatz zu C#? 

Um interessierten Leserinnen und Lesern den Einstieg in Rust zu erleichtern und ein klareres Bild davon zu vermitteln, was Ownership in Rust konkret heißt, haben wir den folgenden Rust-Comic zusammengestellt. Wir hoffen, dass die Codebeispiele und die Kommentare von Ferris, dem Rust-Maskottchen – im Dialog mit dem .NET-Maskottchen dotnet-bot – dabei helfen, die Konzepte der Sprache besser zu verstehen. 

Zusätzlich finden Sie untenstehend exklusiv einen ausführlichen Beitrag von Rainer Stropek zum Konzept des Ownership in Rust.

Viel Spaß beim Lesen!

 

Jetzt für den Newsletter registrieren und den Comic kostenlos downloaden.

Das Ownership-Konzept in Rust

Einführung in das Rust-Ownership-Konzept für C#-Entwickler

Fragt man „Rustaceans“ – so nennt man begeisterte Anwender der Programmiersprache Rust – nach den Besonderheiten der Sprache, kommt das Gespräch recht bald auf Speichersicherheit (Memory Safety). Speichersicherheit ist ein wichtiges Thema in der IT-Industrie. Eine Studie von Microsoft hat beispielsweise ergeben, dass über Jahrzehnte der Anteil an Sicherheitsschwachstellen, die mit Fehlern im Bereich der Speicherverwaltung in Zusammenhang standen, bei rund 70 Prozent konstant blieb. Alle Anstrengungen der letzten Jahre zur Verbesserung der Situation führten zu geringen Erfolgen.
Auf den ersten Blick sind wir als C#-Entwicklerinnen und -Entwickler immun, da .NET Managed Memory und einen Garbage Collector verwendet. Wer glaubt, dass immer nur der native Code in C oder C++ Schwierigkeiten macht, liegt falsch. Auch in C# lassen sich durch Unachtsamkeiten bei der Speicherverwaltung leicht Programmfehler einbauen. Hier einige Beispiele:

  • Mit IDisposable steht in C# ein Interface zur Verfügung, mit dem man als Entwicklerin Objekte explizit freigeben kann, wenn man sie nicht mehr braucht. Ein unbeabsichtigter Zugriff auf ein Objekt, das bereits zuvor durch Aufruf von IDisposable.Dispose freigegeben wurde, führt zu einer Exception. Diese Kategorie von Problemen bezeichnet man als „Use After Free“-Probleme.
  • Wer performante Software in C# schreiben will, greift zur Optimierung der Speicherverwaltung auf Klassen wie ArrayPool oder MemoryPool zurück. Man kann sich von dort größere Felder „ausborgen“ und diese wieder zurückgeben, wenn sie nicht mehr gebraucht werden. Der C#-Compiler erkennt zur Übersetzungszeit nicht, falls man unabsichtlich ein Feld mehrfach zurückgibt. Stattdessen hat man mit einer Exception zur Laufzeit zu kämpfen. Diese Probleme bezeichnet man als „Double Free“-Probleme.
  • Besonders leicht kommt es bei C# zu Speicherverwaltungsproblemen bei nebenläufiger Programmierung. Den meisten C#-Entwicklerinnen und -Entwicklern ist es schon passiert, dass sie eine Liste von Objekten im Speicher hatten, diese mit LINQ oder foreach durchliefen und unabsichtlich gleichzeitig den Inhalt der Liste manipulierten. Auch in einem solchen Fall kommt es zu einer Exception zur Laufzeit.

C#-Ownership-Regeln
Zugegeben, in C oder C++ kommt es viel leichter zu Fehlern bei der Speicherverwaltung als bei Plattformen mit verwaltetem Speicher wie C# oder Java. Die Beispiele oben zeigen aber, dass sie keineswegs vor Speicherfehlern gefeit sind. Man kann argumentieren, dass durch striktes Befolgen der in den jeweiligen Programmiersprachen geltenden Regeln Fehler vermieden werden können. Als Programmiererinnen und Programmierer müssen wir einfach aufmerksamer werden, dann wird alles besser. Die oben zitierte Studie zeigt, dass selbst bei führenden Firmen wie Microsoft, in denen sehr gut ausgebildete Personen Code schreiben, menschliche Fehler unvermeidlich sind. Die Regeln, die man befolgen muss, sind zwar auf den ersten Blick einfach. Die Anwendung in komplexen Codebasen ist jedoch für uns Menschen schwierig.
Bleiben wir beim Beispiel C# und .NET. Microsoft hat in der Dokumentation Regeln für den Umgang mit Speicher in Form von Memory und Span vorgegeben. Werfen wir einen Blick auf die in diesem Zusammenhang wichtigsten drei Konzepte:

1. Jeder Speicherpuffer hat zu jedem Zeitpunkt genau einen Owner. Der Owner ist für das Freigeben des Speichers verantwortlich. Am Beginn ist die Komponente Owner, die den Speicher allokiert hat. Sie kann die Ownership an andere Komponenten übertragen, darf in diesem Fall aber nach der Übertragung nicht mehr auf den Speicher zugreifen.
2. Jeder Speicherpuffer hat Consumer. Ein Consumer muss nicht unbedingt auch der Owner des Speichers sein. Consumer können lesen und eventuell auch schreiben. Bei Schreibzugriffen ist bei mehreren Konsumenten Synchronisation notwendig.
3. Die Zeitdauer, während der ein Consumer auf den Speicher zugreifen kann, bezeichnet man als Lease.

Die von Microsoft definierten Regeln für die Speicherverwaltung bauen auf diesen drei Grundkonzepten auf. Wichtig ist, dass Verletzungen der Regeln nicht vom Compiler erkannt werden, sondern sie erst zur Laufzeit zu Fehlern führen (können).
Rust garantiert Speichersicherheit
Genau wie C# hat auch Rust Ownership-Regeln. Vergleicht man die Regeln der beiden Programmiersprachen, findet man viele Parallelen. Es gibt jedoch einen fundamentalen Unterschied: Rust überprüft die Einhaltung der Regeln zur Übersetzungszeit, nicht zur Laufzeit. Wenn der Code also erfolgreich kompiliert, garantiert Rust sowohl Typsicherheit als auch Speichersicherheit. Laufzeitfehler, die auf diese Themen zurückzuführen sind, sind in Rust ausgeschlossen. Ähnliches gilt auch für nebenläufige Programmierung. Rust eliminiert auch in diesem Bereich einen großen Teil der Laufzeitfehler.
Die Garantie von Speichersicherheit bei gleichzeitigem Verzicht auf verwalteten Speicher und Garbage Collector ist ein riesiger Vorteil von Rust. Rust wird auch (aber keinesfalls nur) als Systemprogrammiersprache eingesetzt. Das war bisher die unangefochtene Domäne von C. Die Speichersicherheit von Rust ist es, die das Potenzial hat, Betriebssysteme, Treiber und andere systemnahe Komponenten in Zukunft deutlich sicherer zu machen.
Einstiegshürde
Das Ownership-Konzept von Rust hat aber nicht nur Vorteile. Viele Einsteigerinnen und Einsteiger in Rust haben am Beginn Schwierigkeiten mit den ungewohnten Regeln. Ständig findet der Compiler etwas, worüber er sich beschwert. Man zweifelt irgendwann an seinen Programmierkenntnissen, wenn man bei den ersten anspruchsvolleren Codebeispielen einfach nicht zu korrekt übersetzbarem Rust-Code kommt.
In diesem Fall heißt es aber, nicht aufzugeben, denn für den steinigen Weg wird man durch Software belohnt, die weniger Laufzeitfehler hat. Das heißt auch, dass man weniger Zeit mit Fehlersuche und Debugging verbringt.

Rainer Stropek
Rainer Stropek
www.timecockpit.com

Rainer Stropek ist IT-Unternehmer, Softwareentwickler, Trainer, Autor und Vortragender im Microsoft-Umfeld. Er ist seit 2010 MVP für Microsoft Azure und entwickelt mit seinem Team die Zeiterfassung für Dienstleistungsprofis time cockpit.