Systemnahes Programmieren
Rust ist eine Sprache für die Systemprogrammierung. Diese ist heutzutage den meisten Entwicklern weitgehend unbekannt. Dabei ist sie der Schlüssel zum Verständnis, wie ein Computer wirklich funktioniert.
Es gibt keine allgemeingültige verbindliche Definition für Systemprogrammierung, deshalb hier ein paar Aussagen, die für sich genommen stimmen:
- Abgrenzung zur Anwendungsprogrammierung
- Fokus auf das Betriebssystem
- Enge Zusammenarbeit mit Betriebssystem-Diensten
Kurzum, bei der Systemprogrammierung, zählt jedes Byte und jeder CPU-Takt. Zur Ausführung einer einfachen Anwendung ist eine erstaunlich große Menge an Systemcode erforderlich.
Um Rust vollständig zu verstehen, müssen Konzepte der Systemprogrammierung erlernt werden, die in anderen Sprachen oft verborgen bleiben. Das mag anfangs unnötig kompliziert erscheinen, bietet aber einen entscheidenden Vorteil:
Rust zwingt einen dazu, den Computer zu verstehen.
Selbst wenn das Ziel nicht ist, später hauptberuflich in Rust zu programmieren, ist dieses Wissen hilfreich. Wer versteht, wie Speicherverwaltung (Stack vs. Heap) funktioniert und was Zeiger sind, schreibt auch in Hochsprachen wie Python, JavaScript oder C# effizienteren und besseren Code. Rust macht diese Konzepte im Vergleich zu Sprachen wie C und C++ besonders zugänglich, weil der Compiler Anfänger durch seine strengen Regeln und präzisen Fehlermeldungen dazu zwingt, die korrekte Verwendung von Speicher und Ownership zu verstehen, bevor der Code überhaupt kompiliert und damit ein Fehler zur Laufzeit möglich wird.
Rust kann daher nicht nur als Programmiersprache gesehen werden, sondern als ein Lernwerkzeug.
Rust behebt dabei viele der historischen Probleme, die die Systemprogrammierung seit Jahrzehnten plagen, ohne die Kontrolle über die Hardware aufzugeben.
Kontrolle vs. Sicherheit
Abschnitt betitelt „Kontrolle vs. Sicherheit“Beginnen wir dieses Kapitel mit einem Beispiel in C. Diese gilt als der Goldstandard der Systemprogrammierung. Viele große Projekte wie z. B. der Linux-Kernel, Windows, Python und unzählige mehr sind in C geschrieben.
Was passiert in folgendem C-Programm?
int main(int argc, char **argv) { unsigned long a[1]; a[3] = 0xC0FFEE; return 0;}Die Ausgabe beim Ausführen des Programms ist ernüchternd:
$ tcc -run main.c # tcc ist ein einfacher C-CompilerSegmentation fault (core dumped) tcc -run test.cDas Programm stürzt ab, da wir auf den Index a[3] zugreifen, obwohl das Array
a nur ein Element umfasst.
Dies ist nach dem C-Standard1 ein undefiniertes Verhalten (undefined behavior).
Das Programm greift ungeprüft auf Speicher zu, der ihm nicht zugewiesen wurde.
Hochsprachen wie Python hingegen fangen diese Art von Fehlern ab. In diesen findet eine Überprüfung zur Laufzeit statt, die bei jedem Zugriff kontrolliert, ob der Index innerhalb der zulässigen Array-Grenzen liegt:
a = [1, 2, 3]
a[5] = 0xC0FFEEDas Resultat ist hier kein Absturz, sondern eine klare Fehlermeldung:
$ python main.pyTraceback (most recent call last): File "/home/hannes/test.py", line 3, in <module> arr[5] = 0xC0FFEE ~~~^^^IndexError: list assignment index out of rangeDieses Beispiel zeigt das Hauptproblem der Systemprogrammierung. C bietet maximale Performance und die volle Kontrolle über den Speicher, zum Preis des ständigen Risikos von undefiniertem Verhalten und Abstürzen, sobald dem Programmierer ein Fehler unterläuft. Sprachen wie Python hingegen erkaufen ihre Sicherheit durch ständige Überprüfungen zur Laufzeit, was Rechenleistung kostet.
Sicherheit ohne Kompromisse
Abschnitt betitelt „Sicherheit ohne Kompromisse“Lange Zeit schien es, als müsste man sich zwischen Effizienz und Sicherheit entscheiden. Genau an dieser Schnittstelle steht Rust. Es verspricht, die Speichersicherheit von Hochsprachen, ohne auf die Systemnähe und Geschwindigkeit von C zu verzichten.
Der entscheidende Unterschied: Fehler werden nicht erst zur Laufzeit, sondern bereits zur Kompilierzeit ausgeschlossen.
fn main() { let mut a: [u64; 1] = [0xC0FFEE];
a[3] = 0xDEADBEEF;}Der Rust-Compiler gibt eine klare und präzise Fehlermeldung aus, und das Programm lässt sich gar nicht erst kompilieren:
error: this operation will panic at runtime |6 | a[3] = 0xDEADBEEF; | ^^^^ index out of bounds: the length is 1 but the index is 3 | = note: `#[deny(unconditional_panic)]` on by default
error: could not compileFootnotes
Abschnitt betitelt „Footnotes“-
Der C-Standard definiert die Syntax, Semantik sowie die Standardbibliothek und legt exakt fests, welches Verhalten garantiert ist und welches als undefined behavior dem Compiler überlassen bleibt. ↩