iX 5/2021
S. 46
Titel
Softwareentwicklung

Rust-Tutorial, Teil 1: Sprachkonstrukte, Ownership und asynchrone Programmierung

Zirkeltraining

Dr. Jens Breitbart, Dr. Stefan Lankes

Rust arbeitet ohne Laufzeitumgebung und Garbage Collector. Mit dem Ownership-Konzept gewährt die Sprache Speichersicherheit, ohne Performance einzubüßen.

Das 2010 entstandene Rust-Projekt hat es sich zum Ziel gesetzt, eine praktikable, sichere und nebenläufi­ge Systemsprache zu entwickeln, mit der Anwendungen dieselbe Laufzeitgeschwindigkeit und Speichereffizienz erhalten wie in C erstellte Programme. Dieses zweiteilige Tutorial gibt eine Einführung in die wichtigsten Konstrukte der Sprache, erläutert das Ownership-Konzept und beschäftigt sich mit der Speicherverwaltung. Der erste Teil stellt Tupel, Enums und Traits vor und zeigt am Beispiel eines Webservers die Vorteile der asynchronen Programmierung.

Die Rust-Syntax ähnelt der von C und C++ insofern, als Entwicklerinnen und Entwickler aufeinanderfolgende Anweisungen mit Semikolon voneinander trennen und Blöcke über geschweifte Klammern markieren:

Hello World in Rust
fn main() {
   println!("Hello, world!");
}

Allerdings weicht die Syntax auch von der von C ab, wie die Variablendeklaration zeigt:

let x: Typ = Wert;
let mut v: Typ = Initialwert;
v = NeuerWert;

Zudem gibt es einen semantischen Un­terschied: Variablen sind in der Regel unveränderlich, es sei denn, man markiert sie über das Schlüsselwort mut als mutable. Den Versuch, der Variablen x einen neuen Wert zuzuweisen, erkennt der Compiler und gibt eine Fehlermeldung aus. Die Angabe des Typs ist dabei optional, zumindest solange er sich über die Zuweisung bestimmen lässt. So erzeugt let x = 42 eine 32-Bit-Ganzzahlvariable mit Vorzeichen (Rust nennt diesen Typ i32), da Ganzzahlwerte ohne weiteres Suffix als solche interpretiert werden.

Einen Check vornehmen

Ganzzahlwerte können über ein Suffix andere Typen angeben: 42u64 ist die Zahl 42 als 64-Bit-Wert ohne Vorzeichen und entsprechend lässt sich mit let x = 42u64 oder let x: u64 = 42 eine 64-Bit-Variable ohne Vorzeichen anlegen. Weitere Typen zeigt die Tabelle. Ungewöhnlich sind dort isize und usize, die immer dieselbe Größe wie der maximal adressierbare Speicherbereich haben, also 64 Bit bei einem für ein 64-Bit-OS kompilierten Programm. Entsprechend sollten Anwender usize für alles benutzen, was direkt oder indirekt mit der Adressierung von Speicher zu tun hat, wie etwa für die Indizierung in Containern. Eine Besonderheit von Rust ist, dass im Debug-Modus kompilierter Code einen Bereichscheck für alle Ganzzahlopera­tionen durchführt und im Falle eines Überlaufs das Programm mit einem Fehler beendet.

Ganzzahl- und Fließkommatypen
Größe Ganzzahl mit Vorzeichen Ganzzahl ohne Vorzeichen Fließkommazahl
8 Bit i8 u8
16 Bit i16 u16
32 Bit i32 u32 f32
64 Bit i64 u64 f64
128 Bit i128 u128
OS- und CPU-Architektur-abhängig isize usize
Die fett hervorgehobenen Typen sind der Standardtyp für Werte ohne Suffix.

Rust bietet verschiedene Möglichkeiten an, zusammengesetzte Werte in einer Variablen abzuspeichern, am einfachsten per Tupel. Der folgende Codeabschnitt zeigt, wie man ein Tupel anlegt und auf Werte zugreifen kann:

let tup = (500, 6.4, 1);
let (x, y, z) = tup;
assert!(x == tup.0);

Auch Arrays kennt die Sprache. Der folgende Code legt zwei identische Arrays a und b an, für das Array b gibt man nur den Typ und seine Größe mit [i32; 5] an:

let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];

let index = 9;
let element = a[index];

Die Variable element versucht, auf das zehnte Element von Array a zuzugreifen, das allerdings nur die Größe 5 besitzt. In vielen Sprachen würde dies zu einem illegalen Speicherzugriff führen. Rust führt aber eben für alle Arrayzugriffe einen Bereichscheck durch. Kompiliert man das Beispiel mit einer alten Version des Rust-­Compi­lers, gibt er zur Laufzeit noch einen Fehler aus. Neue Versionen des Compilers sind hingegen in der Lage zu erkennen, dass im­mer ein Laufzeitfehler ausgelöst würde, und weigern sich, den Code zu kompi­lieren.

Kommentieren