Rust-Tutorial
Rust ist eine Programmiersprache von Mozilla. Mit Rust lassen sich u. a. Kommandozeilen-Tools, Web-Applikationen und Netzwerkprogramme schreiben. Ferner eignet sich die Sprache für hardwarenahe Programmierung. Unter Rust-Programmierern erfreut sich die Sprache ausgesprochener Beliebtheit.
In diesem Rust-Tutorial zeigen wir Ihnen die wichtigsten Features der Sprache. Dabei gehen wir auf Gemeinsamkeiten und Unterschiede zu anderen, verbreiteten Sprachen ein. Wir leiten Sie durch die Rust-Installation, und Sie lernen, Rust-Code auf Ihrem eigenen System zu schreiben und zu kompilieren.
Die Programmiersprache Rust im Überblick
Bei Rust handelt es sich um eine kompilierte Sprache. Diese Eigenschaft resultiert in hoher Performanz; gleichzeitig bietet die Sprache ausgereifte Abstraktionen, die dem Programmierer die Arbeit erleichtern. Ein besonderer Fokus von Rust liegt auf der Speichersicherheit. Damit hat die Sprache einen besonderen Vorteil im Vergleich zu älteren Sprachen wie C und C++.
Rust auf dem eigenen System nutzen
Da Rust eine freie und quelloffene Software (FOSS) ist, kann ein jeder die Rust-Toolchain herunterladen und auf dem eigenen System nutzen. Anders als Python oder JavaScript ist Rust keine interpretierte Sprache. Statt einem Interpreter kommt, wie bei C, C++ und Java, ein Compiler zum Einsatz. In der Praxis bedeutet dies, dass zum Ausführen von Code zwei Schritte gehören:
- Den Quelltext kompilieren. Dies erzeugt eine ausführbare Binärdatei.
- Die resultierende Binärdatei ausführen.
Beide Schritte werden im einfachsten Fall über die Kommandozeile gesteuert.
In einem anderen Digital-Guide-Artikel schauen wir uns den Unterschied zwischen Compiler und Interpreter genauer an.
Mit Rust können neben ausführbaren Binärdateien auch Bibliotheken erzeugt werden. Handelt es sich beim kompilierten Code um ein direkt ausführbares Programm, muss im Quelltext eine main()-Funktion definiert werden. Wie in C / C++ dient diese als Einstiegspunkt in die Code-Ausführung.
Rust für das Tutorial auf dem lokalen System installieren
Um Rust nutzen zu können, benötigen Sie zunächst eine lokale Installation. Unter macOS nutzen Sie dabei den Homebrew Paketmanager. Homebrew funktioniert auch unter Linux. Öffnen Sie eine Kommandozeile (‚Terminal.App‘ auf dem Mac), kopieren Sie die folgende Codezeile in das Terminal und führen Sie diese aus:
brew install rust
Um Rust auf Windows oder einem anderen System ohne Homebrew zu installieren, nutzen Sie das offizielle Tool Rustup.
Um zu überprüfen, ob die Rust-Installation erfolgreich war, öffnen Sie ein neues Fenster auf der Kommandozeile und führen Sie den folgenden Code aus:
rustc --version
Sofern Rust korrekt auf Ihrem System installiert wurde, wird Ihnen die Version des Rust-Compilers angezeigt. Sollte stattdessen eine Fehlermeldung erscheinen, starten Sie die Installation ggf. erneut.
Rust-Code kompilieren
Zum Kompilieren von Rust-Code benötigen Sie eine Rust-Quelltextdatei. Öffnen Sie die Kommandozeile und führen Sie die nachfolgenden Code-Stücke aus. Wir werden zunächst einen Ordner für das Rust-Tutorial auf dem Desktop anlegen und in den Ordner wechseln:
cd "$HOME/Desktop/"
mkdir rust-tutorial && cd rust-tutorial
Als nächstes erzeugen wir die Rust Quelltext-Datei für ein simples „Hello, World“-Beispiel:
cat << EOF > ./rust-tutorial.rs
fn main() {
println!("Hello, World!");
}
EOF
Rust-Quelltextdateien enden mit dem Kürzel .rs.
Abschließend werden wir den Rust-Quelltext kompilieren und die resultierende Binärdatei ausführen:
# Rust-Quelltext kompilieren
rustc rust-tutorial.rs
# resultierende Binärdatei ausführen
./rust-tutorial
Nutzen Sie in den Befehl rustc rust-tutorial.rs && ./rust-tutorial, um die beiden Schritte zu kombinieren. So können Sie auf der Kommandozeile durch Drücken der Pfeiltaste nach oben gefolgt von Enter Ihr Programm erneut kompilieren und ausführen.
Rust-Pakete mit Cargo verwalten
Neben der eigentlichen Rust-Sprache gibt es eine Vielzahl externer Pakete. Diese sogenannten Crates lassen sich von der Rust Package Registry beziehen. Dazu wird das zusammen mit Rust installierte Tool Cargo verwendet. Der cargo-Befehl wird auf der Kommandozeile genutzt und erlaubt neben der Installation von Paketen auch das Erstellen neuer Pakete. Überprüfen Sie, ob Cargo korrekt installiert wurde:
cargo --version
Rust-Grundlagen lernen
Um Rust zu lernen, empfehlen wir Ihnen, die Code-Beispiele selbst auszuprobieren. Sie können dafür die bereits angelegte Datei rust-tutorial.rs nutzen. Kopieren Sie ein Code-Beispiel in die Datei, kompilieren Sie diese und führen Sie die resultierende Binärdatei aus. Damit dies funktioniert, muss der Beispiel-Code innerhalb der main()-Funktion eingefügt werden!
Sie können auch den Rust Playground direkt in Ihrem Browser nutzen, um Rust-Code auszuprobieren.
Anweisungen und Blöcke
Anweisungen sind grundlegender Code-Baustein in Rust. Eine Anweisung endet mit einem Semikolon (;) und gibt, anders als ein Ausdruck, keinen Wert zurück. Mehrere Anweisungen können in einem Block gruppiert werden. Blöcke werden wie in C/C++ und Java von geschweiften Klammern '{}' begrenzt.
Kommentare in Rust
Kommentare sind ein wichtiges Feature einer jeden Programmiersprache. Sie dienen sowohl zum Dokumentieren des Codes als auch zur Planung, bevor der eigentlich Code geschrieben wird. Rust verwendet dieselbe Kommentar-Syntax wie C, C++, Java und JavaScript: Jeglicher Text nach einem doppelten Schrägstrich wird als Kommentar interpretiert und vom Compiler ignoriert:
// Hier steht ein Kommentar
// Ein Kommentar,
// der sich über
// mehrere Zeilen
// erstreckt.
Variablen und Konstanten
Wir nutzen in Rust das Schlüsselwort ‚let‘, um eine Variable zu deklarieren. Eine bestehende Variable kann in Rust erneut deklariert werden und „überschattet“ dann die bestehende Variable. Im Unterschied zu vielen anderen Sprachen kann der Wert einer Variable nicht ohne weiteres geändert werden:
// Variable ‚alter‘ deklarieren und Wert auf ‚42‘ setzen
let alter = 42;
// Wert der Variable ‚alter‘ kann nicht verändert werden
alter = 49; // Kompilier-Fehler
// mit erneutem ‚let‘ kann die Variable überschrieben werden
let alter = 49;
Um den Wert einer Variable als im Nachhinein änderbar zu markieren, bringt Rust das ‚mut‘-Schlüsselwort mit. Der Wert einer mit ‚mut‘ deklarierten Variablen lässt sich verändern:
let mut gewicht = 78;
gewicht = 75;
Mit dem Schlüsselwort ‚const‘ wird eine Konstante erzeugt. Der Wert einer Rust-Konstante muss bei der Kompilierung bekannt sein. Ferner muss der Typ explizit angegeben werden:
const VERSION: &str = "1.46.0";
Der Wert einer Konstante kann nicht verändert werden – eine Konstante lässt sich auch nicht als ‚mut‘ deklarieren. Ferner lässt sich eine Konstante nicht neu deklarieren:
// Konstante definieren
const MAX_NUM: u8 = 255;
MAX_NUM = 0; // Kompilier-Fehler, da der Wert einer Konstante nicht veränderbar ist
const MAX_NUM = 0; // Kompilier-Fehler, da Konstante nicht neu deklariert werden kann
Konzept des Besitztums in Rust
Eines der ausschlaggebenden Features von Rust ist das Konzept des Besitztums (engl. „Ownership“). Das Besitztum ist eng verknüpft mit dem Wert von Variablen, deren „Lifetime“ und der Speicherverwaltung von Objekten auf dem „Heap“-Speicher. Wenn eine Variable den Gültigkeitsbereich („Scope“) verlässt, wird ihr Wert zerstört und der Speicher freigegeben. Rust kommt daher ohne „Garbage Collection“ aus, was zur hohen Performanz beiträgt.
Jeder Wert in Rust gehört einer Variablen – dem Besitzer. Es kann für jeden Wert nur einen Besitzer geben. Wenn der Besitzer den Wert weiterreicht, dann ist er nicht mehr Besitzer:
let name = String::from("Peter Mustermann");
let _name = name;
println!("{}, world!", name); // Kompilier-Fehler, da der Wert von ‚name‘ an ‚_name‘ weitergereicht wurde
Besondere Vorsicht gilt beim Definieren von Funktionen: Wird eine Variable an eine Funktion übergeben, ändert sich der Besitzer des Werts. Die Variable kann nach dem Funktionsaufruf nicht weiterverwendet werden. Hier bedient man sich in Rust eines Tricks: Anstatt den Wert selbst an die Funktion zu übergeben, wird mit dem Ampersand-Symbol (&) eine Referenz deklariert. Damit lässt sich der Wert einer Variable „borgen“. Hier ein Beispiel:
let name = String::from("Peter Mustermann");
// wird der Typ des ‚name‘-Parameters als ‚String‘ statt ‚&String‘ definiert
// kann die Variable ‚name‘ nach dem Funktionsaufruf nicht mehr verwendet werden
fn hallo(name: &String) {
println!("Hallo, {}", name);
}
// das Funktions-Argument muss ebenfalls mit ‚&‘
// als Referenz gekennzeichnet werden
hallo(&name);
// diese Zeile führt ohne Nutzung der Referenz zum Kompilier-Fehler
println!("Hallo, {}", name);
Kontrollstrukturen
Eine grundlegende Eigenschaft der Programmierung besteht darin, den Programmablauf nichtlinear zu gestalten. Ein Programm kann verzweigen, ferner können Programmbestandteile mehrfach ausgeführt werden. Erst durch diese Variabilität wird ein Programm wirklich nutzbar.
Rust verfügt über die in den meisten Programmiersprachen vorhandenen Kontrollstrukturen. Dazu gehören die Schleifen-Konstrukte ‚for‘ und ‚while‘, sowie die Verzweigungen via ‚if‘ und ‚else‘. Daneben bringt Rust auch einige Besonderheiten mit. So erlaubt das ‚match‘-Konstrukt die Zuordnung von Mustern, während die ‚loop‘-Anweisung eine Endlosschleife erzeugt. Um letzteres praktikabel zu machen, wird eine ‚break‘-Anweisung genutzt.
Schleifen
Die wiederholte Ausführung eines Code-Blocks mittels Schleifen ist auch als „Iteration“ bekannt. Oft wird über den Elementen eines Containers iteriert. Wie Python kennt Rust das Konzept des „Iterators“. Ein Iterator abstrahiert den sukzessiven Zugriff auf die Elemente eines Containers. Schauen wir uns ein Beispiel an:
// Liste mit Namen
let namen = ["Jim", "Jack", "John"];
// ‚for‘-Schleife mit Iterator in die Liste
for name in namen.iter() {
println!("Hallo, {}", name);
}
Wie verhält es sich nun, wenn Sie eine ‚for‘-Schleife im Stil von C/C++ oder Java schreiben möchten? Sie möchten also eine Anfangszahl und Endzahl festlegen und alle Werte dazwischen durchlaufen. Für diesen Fall gibt es in Rust, wie auch in Python, das „Range“-Objekt. Dieses erzeugt wiederum einen Iterator, auf dem das ‚for‘-Schlüsselwort operiert:
// die Zahlen ‚1‘ bis ‚10‘ ausgeben
// ‚for‘-Schleife mit ‚range‘-Iterator
// Achtung: Die Range enthält nicht die Endzahl!
for zahl in 1..11 {
println!("Zahl: {}", zahl);
}
// alternative (inklusive) Range-Schreibweise
for zahl in 1..=10 {
println!("Zahl: {}", zahl);
}
Eine ‚while‘-Schleife funktioniert in Rust genauso wie in den meisten anderen Sprachen auch. Es wird eine Kondition festgelegt und der Schleifenkörper so lange ausgeführt, wie die Kondition wahr ist:
// die Zahlen ‚1‘ bis ‚10‘ via ‚while‘-Schleife ausgeben
let mut zahl = 1;
while (zahl <= 10) {
println!(Zahl: {}, zahl);
zahl += 1;
}
Es ist allen Programmiersprachen möglich, mit ‚while‘ eine Endlosschleife zu erzeugen. Normalerweise handelt es sich dabei um einen Fehler, es gibt aber auch Anwendungsfälle, die dies erfordern. Rust kennt für solche Fälle die ‚loop‘-Anweisung:
// Endlosschleife mit ‚while‘
while true {
// …
}
// Endlosschleife mit ‚loop‘
loop {
// …
}
In beiden Fällen kann das ‚break‘-Schlüsselwort verwendet werden, um aus der Schleife auszubrechen.
Verzweigungen
Auch die Verzweigung mit ‚if‘ und ‚else‘ funktioniert in Rust genauso wie in vergleichbaren Sprachen:
const limit: u8 = 42;
let zahl = 43;
if zahl < limit {
println!("Unter dem Limit.");
}
else if zahl == limit {
println!("Genau am Limit…");
}
else {
println!("Über dem Limit!");
}
Interessanter ist Rusts ‚match‘-Schlüsselwort. Dieses erfüllt eine ähnliche Funktion wie die ‚switch‘-Anweisung anderer Sprachen. Schauen Sie sich für ein Beispiel die Funktion karten_symbol() im Abschnitt „Zusammengesetzte Datentypen“ (s. u.) an.
Funktionen, Prozeduren und Methoden
In den meisten Programmiersprachen sind Funktionen grundlegender Baustein modularer Programmierung. Funktionen werden in Rust mit dem Schlüsselwort ‚fn‘ definiert. Dabei wird nicht strikt zwischen den verwandten Konzepten Funktion und Prozedur unterschieden. Beide werden auf nahezu identische Weise definiert.
Eine Funktion im eigentlichen Sinn liefert einen Wert zurück. Wie viele andere Programmiersprachen kennt Rust auch Prozeduren, d. h. Funktionen, die keinen Wert zurückliefern. Als einzige feste Einschränkung gilt, dass der Rückgabetyp einer Funktion explizit angegeben werden muss. Wird kein Rückgabetyp angegeben, kann die Funktion auch keinen Wert zurückgeben; dann handelt es sich der Definition nach um eine Prozedur.
fn prozedur() {
println!("Diese Prozedur liefert keinen Wert zurück.");
}
// eine Zahl negieren
// Rückgabetyp nach dem ‚->‘-Operator
fn negiert(ganzzahl: i8) -> i8 {
return ganzzahl * -1;
}
Neben Funktionen und Prozeduren kennt Rust auch die aus der objektorientierten Programmierung bekannten Methoden. Eine Methode ist eine Funktion, die an eine Datenstruktur gebunden ist. Wie in Python werden Methoden in Rust mit dem ersten Parameter ‚self‘ definiert. Der Aufruf einer Methode erfolgt nach dem üblichen Schema objekt.methode(). Hier ein Beispiel der Methode flaeche(), gebunden an eine ‚struct‘-Datenstruktur:
// ‚struct‘-Definition
struct Rechteck {
breite: u32,
hoehe: u32,
}
// ‚struct‘-Implementierung
impl Rechteck {
fn flaeche(&self) -> u32 {
return self.breite * self.hoehe;
}
}
let rechteck = Rechteck {
breite: 30,
hoehe: 50,
};
println!("Die Fläche des Rechtecks beträgt {}.", rechteck.flaeche());
Datentypen und Datenstrukturen
Bei Rust handelt es sich um eine statisch typisierte Sprache. Anders als in den dynamisch typisierten Sprachen Python, Ruby, PHP oder JavaScript muss in Rust der Typ einer jeden Variable bei der Kompilierung bekannt sein.
Elementare Datentypen
Wie die meisten höheren Programmiersprachen kennt Rust einige elementare Datentypen („Primitives“). Instanzen elementarer Datentypen werden auf dem Stack-Speicher zugewiesen, was besonders performant ist. Ferner lassen sich die Werte elementarer Datentypen per „Literal“-Syntax definieren. Das bedeutet, die Werte können einfach ausgeschrieben werden.
Datentyp | Erklärung | Typ-Annotation |
---|---|---|
Integer | ganze Zahl | i8, u8, etc. |
Floating point | Gleitkommazahl | f64, f32 |
Boolean | Wahrheitswert | bool |
Character | einzelner Unicode-Buchstabe | char |
String | Unicode-Zeichenkette | str |
Obwohl Rust eine statisch typisierte Sprache ist, muss der Typ eines Werts nicht immer explizit deklariert werden. In vielen Fällen kann der Typ vom Compiler aus dem Kontext abgeleitet werden („Type inference“). Alternativ wird der Typ explizit per Typ-Annotation angegeben. In einigen Fällen ist Letzteres zwingend notwendig:
- Der Rückgabetyp einer Funktion muss immer explizit angegeben werden.
- Der Typ einer Konstante muss immer explizit angegeben werden.
- String-Literale müssen speziell behandelt werden, damit deren Größe bei Kompilierung bekannt ist.
Hier ein paar anschauliche Beispiele für die Instanziierung elementarer Datentypen mit Literal-Syntax:
// hier erkennt der Compiler den Typ der Variable automatisch
let cents = 42;
// Typ-Annotation: positive Zahl (‚u8‘ = "unsigned, 8 bits")
let alter: u8 = -15; // Kompilier-Fehler, da negativer Wert gegeben
// Gleitkommazahl
let winkel = 38.5;
// equivalent zu
let winkel: f64 = 38.5;
// Wahrheitswert
let nutzer_angemeldet = true;
// equivalent zu
let nutzer_angemeldet: bool = true;
// Buchstabe, benötigt einzelne Anführungszeichen
let buchstabe = 'a';
// statische Zeichenkette, benötigt doppelte Anführungszeichen
let name = "Walther";
// mit explizitem Typ
let name: &'static str = "Walther";
// alternativ als dynamischer ‚String‘ mit ‚String::from()‘
let name: String = String::from("Walther");
Zusammengesetzte Datentypen
Elementare Datenypen bilden einzelne Werte ab, wohingegen zusammengesetzte Datentypen mehrere Werte bündeln. Rust stellt dem Programmierer eine Handvoll zusammengesetzter Datentypen zur Verfügung.
Die Instanzen zusammengesetzter Datentypen werden, wie Instanzen elementarer Datentypen, auf dem Stack zugewiesen. Um dies zu ermöglichen, müssen die Instanzen eine feste Größe haben. Daraus folgt auch, dass sie sich nach der Instanziierung nicht beliebig verändern lassen. Hier eine Übersicht der wichtigsten zusammengesetzten Datentypen in Rust:
Datentyp | Erklärung | Typ der Elemente | Literal-Syntax |
---|---|---|---|
Array | Liste mehrerer Werte | selber Typ | [a1, a2, a3] |
Tuple | Anordnung mehrerer Werte | jeglicher Typ | (t1, t2) |
Struct | Gruppierung mehrerer benannter Werte | jeglicher Typ | – |
Enum | Aufzählung | jeglicher Typ | – |
Schauen wir uns zunächst eine ‚struct‘-Datenstruktur an. Wir definieren eine Person mit drei benannten Feldern:
struct Person = {
vorname: String,
nachname: String,
alter: u8,
}
Um eine konkrete Person abzubilden, instanziieren wir die ‚struct‘:
let spieler = Person {
vorname: String::from("Peter"),
nachname: String::from("Mustermann"),
alter: 42,
};
// auf Feld einer ‚struct‘-Instanz zugreifen
println!("Alter des Spielers ist: {}", spieler.alter);
Ein ‚enum‘ (kurz für „Enumeration”, zu Deutsch „Aufzählung”) bildet mögliche Varianten einer Eigenschaft ab. Wir stellen dies hier am simplen Beispiel der vier möglichen Farben einer Spielkarte dar:
enum KartenFarbe {
Kreuz,
Pik,
Herz,
Karo,
}
// die Farbe einer konkreten Spielkarte
let farbe = KartenFarbe::Kreuz;
Rust kennt das ‚match‘-Schlüsselwort für „Pattern matching“, zu Deutsch „Muster-Zuordnung“. Die Funktionalität ist vergleichbar mit der ‚switch‘-Anweisung anderer Sprachen. Hier ein Beispiel:
// das zu einer Kartenfarbe gehörige Symbol ermitteln
fn karten_symbol(farbe: KartenFarbe) -> &'static str {
match farbe {
KartenFarbe::Kreuz => "♣︎",
KartenFarbe::Pik => "♠︎",
KartenFarbe::Herz => "♥︎",
KartenFarbe::Karo => "♦︎",
}
}
println!("Symbol: {}", karten_symbol(KartenFarbe::Kreuz)); // gibt Symbol ♣︎ aus
Ein Tupel ist eine Anordnung mehrerer Werte, die unterschiedlichen Typs sein können. Die einzelnen Werte des Tupels können durch Destrukturierung mehreren Variablen zugewiesen werden. Benötigt man einen der Werte nicht, wird – wie in Haskell, Python und JavaScript üblich – der Unterstrich (_) als Platzhalter verwendet. Hier ein Beispiel:
// Spielkarte als Tupel definieren
let spielkarte: (KartenFarbe, u8) = (KartenFarbe::Herz, 7);
// die Werte eines Tupels mehreren Variablen zuweisen
let (farbe, wert) = spielkarte;
// sollten wir nur den Wert benötigen
let (_, wert) = spielkarte;
Da Tupel-Werte geordnet sind, ist auch der Zugriff über einen numerischen Index möglich. Die Indizierung erfolgt nicht in eckigen Klammern, sondern über eine Punkt-Syntax. In den meisten Fällen dürfte Destrukturierung zu besser lesbarem Code führen:
let name = ("Peter", "Mustermann");
let vorname = name.0;
let nachname = name.1;
Höhere Programmierkonstrukte in Rust lernen
Dynamische Datenstrukturen
Die bereits vorgestellten zusammengesetzten Datentypen haben gemeinsam, dass ihre Instanzen auf dem Stack zugewiesen werden. Rusts Standardbibliothek enthält ferner eine Reihe häufig gebrauchter dynamischer Datenstrukturen. Instanzen dieser Datenstrukturen werden auf dem Heap zugewiesen. Daraus folgt, dass die Größe der Instanzen im Nachhinein verändert werden kann. Hier eine kurze Übersicht häufig genutzter dynamischer Datenstrukturen:
Datentyp | Erklärung |
---|---|
Vector | dynamische Liste mehrerer Werte desselben Typs |
String | dynamische Folge von Unicode-Buchstaben |
HashMap | dynamische Zuordnung von Schlüssel-Wert-Paaren |
Hier ein Beispiel eines dynamisch wachsenden Vektors:
// Vektor mit ‚mut‘ als veränderbar deklarieren
let mut namen = Vec::new();
// dem Vektor Werte hinzufügen
namen.push("Jim");
namen.push("Jack");
namen.push("John");
Objektorientierte Programmierung (OOP) in Rust
Im Gegensatz zu Sprachen wie C++ und Java kennt Rust kein Konzept von Klassen. Dennoch lässt sich der OOP-Methodologie folgend programmieren. Grundlage sind die bereits vorgestellten Datentypen. Insbesondere der ‚struct‘-Typ kann benutzt werden, um die Struktur von Objekten zu definieren.
Ferner gibt es in Rust die „Traits“, zu Deutsch „Merkmale“. Ein Trait bündelt eine Menge von Methoden, die dann vom einem beliebigen Typ implementiert werden können. Dabei umfasst ein Trait Methoden-Deklarationen, kann jedoch auch Implementationen enthalten. Konzeptuell liegt ein Trait damit in etwa zwischen einem Java-Interface und einer abstrakten Basisklasse.
Ein bestehender Trait kann von verschiedenen Typen implementiert werden. Überdies kann ein Typ mehrere Traits implementieren. Rust erlaubt somit das Komponieren von Funktionalität für verschiedene Typen, ohne dass diese von einem gemeinsamen Vorfahren erben müssen.
Meta-Programmierung
Wie viele andere Programmiersprachen erlaubt auch Rust, Code für Meta-Programmierung zu schreiben. Dabei handelt es sich um Code, der weiteren Code erzeugt. Dazu gehören in Rust zum einen die „Macros“, die Sie vielleicht so ähnlich aus C/C++ kennen. Macros enden mit einem Ausrufungszeichen (!); das Macro ‚println!‘ zur Ausgabe von Text auf der Kommandozeile ist Ihnen in diesem Artikel schon mehrfach untergekommen.
Zum anderen kennt Rust auch „Generics“. Diese erlauben das Schreiben von Code, der über mehrere Typen abstrahiert. Generics sind in etwa vergleichbar mit den Templates in C++ oder den gleichnamigen Generics in Java. Ein in Rust häufig genutztes Generic ist ‚Option<T>‘, was für einen beliebigen Typ ‚T‘ die Dualität ‚None‘/‚Some(T)‘ abstrahiert.
Rust hat das Potenzial, als System-Programmiersprache die alteingesessenen Favoriten C und C++ zu ersetzen.