19 April 2025
Rust weckt durchweg das Interesse von Entwicklern und wurde in den Stack Overflow Surveys mehrere Jahre in Folge zur „beliebtesten“ Programmiersprache gekürt. Das ist nicht nur Hype; Rust bietet eine überzeugende Mischung aus Performance, Sicherheit und modernen Sprachmerkmalen, die gängige Schwachstellen anderer Systemprogrammiersprachen adressieren. Wenn du neugierig bist, was Rust besonders macht und deine Reise beginnen möchtest, bietet dieser Einsteigerleitfaden das grundlegende Wissen, um loszulegen. Wir werden die Kernsyntax, einzigartige Konzepte wie Ownership und die essentiellen Werkzeuge erkunden, die das Rust-Ökosystem antreiben.
Rust positioniert sich als Sprache zur Entwicklung zuverlässiger und effizienter Software. Seine Hauptvorteile drehen sich um Speichersicherheit ohne Abhängigkeit von einem Garbage Collector und die Ermöglichung von Fearless Concurrency. Hier sind Gründe, warum du erwägen solltest, Rust zu lernen:
Bevor du deine erste Zeile Rust-Code schreibst, musst du die Rust-Toolchain einrichten. Der standardmäßige und empfohlene Weg zur Installation von Rust ist die Verwendung von rustup
, dem Rust-Toolchain-Installer.
rustup
: Dieses Kommandozeilen-Tool verwaltet deine Rust-Installationen. Es ermöglicht dir, verschiedene Rust-Versionen (wie Stable-, Beta- oder Nightly-Builds) zu installieren, zu aktualisieren und einfach zwischen ihnen zu wechseln. Besuche die offizielle Rust-Website (https://www.rust-lang.org/tools/install) für Installationsanweisungen spezifisch für dein Betriebssystem.rustc
: Dies ist der Rust-Compiler. Nachdem du deinen Rust-Quellcode in .rs
-Dateien geschrieben hast, kompiliert rustc
diese in ausführbare Binärdateien oder Bibliotheken, die dein Computer verstehen kann. Obwohl entscheidend, wirst du rustc
in deinem täglichen Workflow normalerweise nicht sehr oft direkt aufrufen.cargo
: Dies ist Rusts Build-System und Paketmanager, und es ist das Werkzeug, mit dem du am häufigsten interagieren wirst. Cargo orchestriert viele gängige Entwicklungsaufgaben:
cargo new
).cargo build
).cargo run
).cargo test
).Cargo.toml
-Datei aufgeführt sind).Für schnelles Experimentieren ohne lokale Installation sind Online-Plattformen wie der offizielle Rust Playground oder integrierte Entwicklungsumgebungen (IDEs) wie Replit ausgezeichnete Optionen.
Beginnen wir mit dem traditionellen „Hallo, Welt!“-Programm, einem Initiationsritus beim Lernen jeder neuen Sprache. Erstelle eine Datei namens main.rs
und füge den folgenden Code hinzu:
fn main() {
// This line prints text to the console
println!("Hello, Rust!");
}
Um dieses einfache Programm mit den Basiswerkzeugen zu kompilieren und auszuführen:
main.rs
enthält, und führe aus:
rustc main.rs
Dieser Befehl ruft den Rust-Compiler (rustc
) auf, der eine ausführbare Datei erstellt (z. B. main
unter Linux/macOS, main.exe
unter Windows)../main
# Unter Windows: .\main.exe
Für alles, was über eine einzelne Datei hinausgeht, ist die Verwendung von Cargo jedoch der Standard und ein viel bequemerer Ansatz:
cargo new hello_rust
cd hello_rust
Cargo erstellt ein neues Verzeichnis namens hello_rust
, das ein src
-Unterverzeichnis mit main.rs
(bereits mit dem „Hello, Rust!“-Code gefüllt) und eine Konfigurationsdatei namens Cargo.toml
enthält.cargo run
Cargo übernimmt den Kompilierungsschritt und führt dann das resultierende Programm aus und zeigt „Hello, Rust!“ auf deiner Konsole an.Schauen wir uns den Code-Schnipsel genauer an:
fn main()
: Dies definiert die Hauptfunktion. Das fn
-Schlüsselwort signalisiert eine Funktionsdeklaration. main
ist ein spezieller Funktionsname; es ist der Einstiegspunkt, an dem jedes ausführbare Rust-Programm zu laufen beginnt. Die Klammern ()
zeigen an, dass diese Funktion keine Eingabeparameter entgegennimmt.{}
: Geschweifte Klammern definieren einen Codeblock oder Gültigkeitsbereich (Scope). Aller Code, der zur Funktion gehört, steht innerhalb dieser Klammern.println!("Hello, Rust!");
: Diese Zeile führt die Aktion aus, Text auf der Konsole auszugeben.
println!
ist ein Rust-**Makro*. Makros ähneln Funktionen, haben aber einen wesentlichen Unterschied: Sie enden mit einem Ausrufezeichen !
. Makros führen Code-Generierung zur Kompilierzeit durch und bieten mehr Mächtigkeit und Flexibilität als reguläre Funktionen (wie die Verarbeitung einer variablen Anzahl von Argumenten, was println!
tut). Das println!
-Makro gibt den übergebenen Text auf der Konsole aus und fügt automatisch ein Zeilenumbruchzeichen am Ende hinzu."Hello, Rust!"
ist ein *String-Literal – eine feste Zeichensequenz, die Text darstellt und in doppelten Anführungszeichen eingeschlossen ist.;
: Das Semikolon markiert das Ende der Anweisung. Die meisten Zeilen ausführbaren Rust-Codes (Anweisungen) enden mit einem Semikolon.Tauchen wir nun in die fundamentalen Bausteine der Rust-Programmiersprache ein.
Variablen werden verwendet, um Datenwerte zu speichern. In Rust deklarierst du Variablen mit dem let
-Schlüsselwort.
let aepfel = 5;
let nachricht = "Nimm fünf";
Ein Kernkonzept in Rust ist, dass Variablen standardmäßig unveränderlich (immutable) sind. Das bedeutet, sobald ein Wert an einen Variablennamen gebunden ist, kannst du diesen Wert später nicht mehr ändern.
let x = 10;
// x = 15; // Diese Zeile verursacht einen Kompilierfehler! Kann unveränderlicher Variable `x` nicht zweimal zuweisen.
println!("Der Wert von x ist: {}", x); // {} ist ein Platzhalter für den Wert von x
Diese standardmäßige Unveränderlichkeit ist eine bewusste Designentscheidung, die dir hilft, sichereren, vorhersagbareren Code zu schreiben, indem sie versehentliche Änderungen von Daten verhindert, was eine häufige Fehlerquelle sein kann. Wenn du eine Variable benötigst, deren Wert sich ändern kann, musst du sie bei der Deklaration explizit mit dem mut
-Schlüsselwort als mutierbar markieren.
let mut anzahl = 0; // Deklariere 'anzahl' als mutierbar
println!("Ursprüngliche Anzahl: {}", anzahl);
anzahl = 1; // Dies ist erlaubt, da 'anzahl' mit 'mut' deklariert wurde
println!("Neue Anzahl: {}", anzahl);
Rust erlaubt auch Shadowing (Verdeckung). Du kannst eine neue Variable mit demselben Namen wie eine vorherige Variable innerhalb desselben Gültigkeitsbereichs deklarieren. Die neue Variable „verdeckt“ die alte, was bedeutet, dass nachfolgende Verwendungen des Namens sich auf die neue Variable beziehen. Dies unterscheidet sich von der Mutation, da wir eine völlig neue Variable erstellen, die sogar einen anderen Typ haben kann.
let leerzeichen = " "; // 'leerzeichen' ist anfangs ein String Slice (&str)
let leerzeichen = leerzeichen.len(); // 'leerzeichen' wird nun durch eine neue Variable verdeckt, die die Länge enthält (eine Ganzzahl, usize)
println!("Anzahl der Leerzeichen: {}", leerzeichen); // Gibt den Ganzzahlwert aus
Rust ist eine statisch typisierte Sprache. Das bedeutet, der Typ jeder Variablen muss dem Compiler zur Kompilierzeit bekannt sein. Rust verfügt jedoch über eine hervorragende Typinferenz. In vielen Situationen musst du den Typ nicht explizit hinschreiben; der Compiler kann ihn oft anhand des Werts und deiner Verwendung herausfinden.
let menge = 10; // Compiler schließt auf i32 (der Standard-Ganzzahltyp mit Vorzeichen)
let preis = 9.99; // Compiler schließt auf f64 (der Standard-Gleitkommatyp)
let aktiv = true; // Compiler schließt auf bool (Boolean)
let initial = 'R'; // Compiler schließt auf char (Zeichen)
Wenn du explizit sein möchtest oder musst (z. B. zur Klarheit oder wenn der Compiler Hilfe benötigt), kannst du Typ-Annotationen verwenden, indem du einen Doppelpunkt :
gefolgt vom Typnamen angibst.
let punktestand: i32 = 100; // Explizit eine 32-Bit-Ganzzahl mit Vorzeichen
let verhaeltnis: f32 = 0.5; // Explizit eine Gleitkommazahl einfacher Genauigkeit
let ist_fertig: bool = false; // Explizit ein Boolean
let note: char = 'A'; // Explizit ein Zeichen (Unicode-Skalarwert)
Rust hat mehrere eingebaute *skalare Typen (die einzelne Werte repräsentieren):
i8
, i16
, i32
, i64
, i128
, isize
) speichern sowohl positive als auch negative ganze Zahlen. Vorzeichenlose Ganzzahlen (u8
, u16
, u32
, u64
, u128
, usize
) speichern nur nicht-negative ganze Zahlen. Die Typen isize
und usize
hängen von der Architektur des Computers ab (32-Bit oder 64-Bit) und werden hauptsächlich zur Indizierung von Sammlungen verwendet.f32
(einfache Genauigkeit) und f64
(doppelte Genauigkeit). Der Standard ist f64
.bool
-Typ hat zwei mögliche Werte: true
oder false
.char
-Typ repräsentiert einen einzelnen Unicode-Skalarwert (umfassender als nur ASCII-Zeichen), eingeschlossen in einfachen Anführungszeichen (z. B. 'z'
, 'π'
, '🚀'
).String
vs. &str
Der Umgang mit Text in Rust beinhaltet oft zwei primäre Typen, was für Neulinge verwirrend sein kann:
&str
(ausgesprochen „String Slice“): Dies ist eine *unveränderliche Referenz auf eine Sequenz von UTF-8-kodierten Bytes, die irgendwo im Speicher gespeichert sind. String-Literale (wie "Hallo"
) sind vom Typ &'static str
, was bedeutet, dass sie direkt in der Binärdatei des Programms gespeichert sind und für die gesamte Laufzeit des Programms leben. Slices bieten eine *Sicht auf String-Daten, ohne sie zu besitzen. Sie haben eine feste Größe.String
: Dies ist ein *besitzender, *vergrößerbarer, *mutierbarer, UTF-8-kodierter String-Typ. String
-Daten werden auf dem *Heap gespeichert, was es ermöglicht, ihre Größe zu ändern. Du verwendest typischerweise String
, wenn du String-Daten modifizieren musst oder wenn der String seine Daten besitzen und seine eigene Lebensdauer verwalten muss (oft bei der Rückgabe von Strings aus Funktionen oder deren Speicherung in Structs).// String-Literal (im Programm-Binary gespeichert, unveränderlich)
let statischer_slice: &'static str = "Ich bin unveränderlich";
// Einen besitzenden, Heap-allozierten String aus einem Literal erstellen
let mut dynamischer_string: String = String::from("Start");
// Den String modifizieren (möglich, da er mutierbar ist und seine Daten besitzt)
dynamischer_string.push_str(" und wachsen");
println!("{}", dynamischer_string); // Ausgabe: Start und wachsen
// Einen String Slice erstellen, der auf einen Teil des Strings verweist
// Dieser Slice borgt Daten von dynamischer_string
let slice_aus_string: &str = &dynamischer_string[0..5]; // Verweist auf "Start"
println!("Slice: {}", slice_aus_string);
Funktionen sind fundamental, um Code in benannte, wiederverwendbare Einheiten zu organisieren. Wir sind bereits der speziellen main
-Funktion begegnet.
// Funktionsdefinition
fn gruessen(name: &str) { // Nimmt einen Parameter entgegen: 'name', ein String Slice (&str)
println!("Hallo, {}!", name);
}
// Funktion, die zwei i32-Parameter nimmt und ein i32 zurückgibt
fn addiere(a: i32, b: i32) -> i32 {
// In Rust wird der letzte Ausdruck im Funktionsrumpf automatisch zurückgegeben,
// solange er nicht mit einem Semikolon endet.
a + b
// Dies ist äquivalent zu: return a + b;
}
fn main() {
gruessen("Alice"); // Rufe die 'gruessen'-Funktion auf
let summe = addiere(5, 3); // Rufe 'addiere' auf, binde den Rückgabewert an 'summe'
println!("5 + 3 = {}", summe); // Ausgabe: 5 + 3 = 8
}
Wichtige Punkte zu Funktionen:
fn
-Schlüsselwort, um Funktionen zu deklarieren.->
an. Wenn eine Funktion keinen Wert zurückgibt, ist ihr Rückgabetyp implizit ()
(ein leeres Tupel, oft als „Unit-Typ“ bezeichnet).;
enden) und enden optional mit einem *Ausdruck (Expression, etwas, das zu einem Wert ausgewertet wird).return
-Schlüsselwort kann für explizite, frühe Rückgaben von überall innerhalb der Funktion verwendet werden.Rust bietet Standard-Kontrollflussstrukturen, um die Reihenfolge festzulegen, in der Code ausgeführt wird:
if
/else
/else if
: Wird für bedingte Ausführung verwendet.let zahl = 6;
if zahl % 4 == 0 {
println!("Zahl ist durch 4 teilbar");
} else if zahl % 3 == 0 {
println!("Zahl ist durch 3 teilbar"); // Dieser Zweig wird ausgeführt
} else {
println!("Zahl ist nicht durch 4 oder 3 teilbar");
}
// Wichtig: 'if' ist in Rust ein Ausdruck, d.h. es wird zu einem Wert ausgewertet.
// Dies erlaubt dir, es direkt in 'let'-Anweisungen zu verwenden.
let bedingung = true;
let wert = if bedingung { 5 } else { 6 }; // wert wird 5 sein
println!("Der Wert ist: {}", wert);
// Hinweis: Beide Zweige des 'if'-Ausdrucks müssen zum gleichen Typ ausgewertet werden.
loop
: Erstellt eine Endlosschleife. Du verwendest typischerweise break
, um die Schleife zu verlassen, optional mit Rückgabe eines Wertes.while
: Läuft, solange eine angegebene Bedingung wahr bleibt.for
: Iteriert über die Elemente einer Sammlung oder eines Bereichs. Dies ist der am häufigsten verwendete und oft sicherste Schleifentyp in Rust.// loop Beispiel
let mut zaehler = 0;
let ergebnis = loop {
zaehler += 1;
if zaehler == 10 {
break zaehler * 2; // Schleife verlassen und 'zaehler * 2' zurückgeben
}
};
println!("Schleifenergebnis: {}", ergebnis); // Ausgabe: Schleifenergebnis: 20
// while Beispiel
let mut num = 3;
while num != 0 {
println!("{}!", num);
num -= 1;
}
println!("ZÜNDUNG!!!");
// for Beispiel (Iteration über ein Array)
let a = [10, 20, 30, 40, 50];
for element in a.iter() { // .iter() erstellt einen Iterator über die Elemente des Arrays
println!("Der Wert ist: {}", element);
}
// for Beispiel (Iteration über einen Bereich)
// (1..4) erstellt einen Bereich einschließlich 1, 2, 3 (exklusive 4)
// .rev() kehrt den Iterator um
for nummer in (1..4).rev() {
println!("{}!", nummer); // Gibt 3!, 2!, 1! aus
}
println!("ERNEUTE ZÜNDUNG!!!");
Verwende Kommentare, um Erklärungen und Notizen zu deinem Code hinzuzufügen, die der Compiler ignoriert.
// Dies ist ein einzeiliger Kommentar. Er erstreckt sich bis zum Ende der Zeile.
/*
* Dies ist ein mehrzeiliger Blockkommentar.
* Er kann sich über mehrere Zeilen erstrecken und ist nützlich
* für längere Erklärungen.
*/
let glueckszahl = 7; // Man kann Kommentare auch ans Ende einer Zeile setzen.
Ownership ist Rusts charakteristischstes und zentralstes Merkmal. Es ist der Mechanismus, der es Rust ermöglicht, Speichersicherheit zur Kompilierzeit zu garantieren, ohne einen Garbage Collector zu benötigen. Das Verständnis von Ownership ist der Schlüssel zum Verständnis von Rust. Es folgt drei Kernregeln:
{ // s ist hier nicht gültig, es ist noch nicht deklariert
let s = String::from("hallo"); // s ist ab hier gültig;
// s 'besitzt' die String-Daten, die auf dem Heap alloziert wurden.
// Man kann s hier verwenden
println!("{}", s);
} // Der Gültigkeitsbereich endet hier. 's' ist nicht länger gültig.
// Rust ruft automatisch eine spezielle 'drop'-Funktion für den String auf, den 's' besitzt,
// und gibt dessen Heap-Speicher frei.
Wenn du einen besitzenden Wert (wie einen String
, Vec
oder einen Struct, der besitzende Typen enthält) einer anderen Variablen zuweist oder ihn per Wert an eine Funktion übergibst, wird der Besitz (*Ownership) verschoben (moved). Die ursprüngliche Variable wird ungültig.
let s1 = String::from("original");
let s2 = s1; // Das Ownership der String-Daten wird von s1 zu s2 VERSCHOBEN (MOVED).
// s1 gilt danach nicht mehr als gültig.
// println!("s1 ist: {}", s1); // Kompilierfehler! Wert hier nach Verschiebung geborgt.
// s1 besitzt die Daten nicht mehr.
println!("s2 ist: {}", s2); // s2 ist nun der Besitzer und gültig.
Dieses Verschiebeverhalten (Move Behavior) verhindert „Double Free“-Fehler, bei denen zwei Variablen versehentlich versuchen könnten, denselben Speicherort freizugeben, wenn sie den Gültigkeitsbereich verlassen. Primitive Typen wie Ganzzahlen, Gleitkommazahlen, Booleans und Zeichen implementieren den Copy
-Trait, was bedeutet, dass sie bei Zuweisung oder Übergabe an Funktionen einfach kopiert statt verschoben werden.
Was, wenn du eine Funktion einen Wert verwenden lassen möchtest, ohne das Ownership zu übertragen? Du kannst Referenzen erstellen. Das Erstellen einer Referenz wird Borrowing (Borgen/Leihen) genannt. Eine Referenz erlaubt es dir, auf Daten zuzugreifen, die einer anderen Variablen gehören, ohne das Ownership zu übernehmen.
// Diese Funktion nimmt eine Referenz (&) auf einen String entgegen.
// Sie borgt sich den String, übernimmt aber nicht das Ownership.
fn berechne_laenge(s: &String) -> usize {
s.len()
} // Hier verlässt s (die Referenz) den Gültigkeitsbereich. Aber da sie nicht die
// String-Daten besitzt, werden die Daten NICHT gedroppt, wenn die Referenz den Gültigkeitsbereich verlässt.
fn main() {
let s1 = String::from("hallo");
// Wir übergeben eine Referenz auf s1 mit dem '&'-Symbol.
// s1 besitzt weiterhin die String-Daten.
let laenge = berechne_laenge(&s1);
// s1 ist hier immer noch gültig, da das Ownership nie verschoben wurde.
println!("Die Länge von '{}' ist {}.", s1, laenge);
}
Referenzen sind standardmäßig unveränderlich, genau wie Variablen. Wenn du die geborgten Daten modifizieren möchtest, benötigst du eine mutierbare Referenz, gekennzeichnet durch &mut
. Rust erzwingt jedoch strenge Regeln für mutierbare Referenzen, um Datenwettläufe (Data Races) zu verhindern:
Die Borrowing-Regeln:
&mut T
).&T
).Diese Regeln werden vom Compiler erzwungen.
// Diese Funktion nimmt eine mutierbare Referenz auf einen String entgegen
fn aendere(ein_string: &mut String) {
ein_string.push_str(", Welt");
}
fn main() {
// 's' muss als mutierbar deklariert werden, um mutierbares Borrowing zu erlauben
let mut s = String::from("hallo");
// Beispiel für die Durchsetzung der Borrowing-Regeln (Zeilen auskommentieren, um Fehler zu sehen):
// let r1 = &s; // unveränderliches Borrow - OK
// let r2 = &s; // weiteres unveränderliches Borrow - OK (mehrere unveränderliche Borrows erlaubt)
// let r3 = &mut s; // FEHLER! Kann `s` nicht mutierbar borgen, während unveränderliche Borrows aktiv sind
// // Rust erzwingt: entweder mehrere Leser (&T) ODER ein einzelner Schreiber (&mut T), niemals beides
// println!("{}, {}", r1, r2); // Die Verwendung von r1/r2 hält sie aktiv und löst den Fehler aus
// // Ohne dieses println würde Rusts NLL (Non-Lexical Lifetimes) r1/r2 frühzeitig freigeben,
// // wodurch `&mut s` hier gültig wäre
// Ein mutierbares Borrow ist hier erlaubt, da keine anderen Borrows aktiv sind
aendere(&mut s);
println!("{}", s); // Ausgabe: hallo, Welt
}
Rust bietet Möglichkeiten, mehrere Werte zu komplexeren Typen zu gruppieren.
Structs (kurz für Strukturen) ermöglichen es dir, benutzerdefinierte Datentypen zu definieren, indem du verwandte Datenfelder unter einem einzigen Namen gruppierst.
// Einen Struct namens User definieren
struct User {
active: bool,
username: String, // Verwendet den besitzenden String-Typ
email: String,
sign_in_count: u64,
}
fn main() {
// Eine Instanz des User-Structs erstellen
// Instanzen müssen Werte für alle Felder angeben
let mut user1 = User {
email: String::from("jemand@example.com"),
username: String::from("einBenutzername123"),
active: true,
sign_in_count: 1,
};
// Auf Struct-Felder mittels Punktnotation zugreifen
// Die Instanz muss mutierbar sein, um Feldwerte zu ändern
user1.email = String::from("andereemail@example.com");
println!("User E-Mail: {}", user1.email);
// Verwendung einer Hilfsfunktion zum Erstellen einer User-Instanz
let user2 = erstelle_user(String::from("user2@test.com"), String::from("user2"));
println!("User 2 Aktiv-Status: {}", user2.active);
// Struct-Update-Syntax: Erstellt eine neue Instanz unter Verwendung einiger Felder
// einer vorhandenen Instanz für die restlichen Felder.
let user3 = User {
email: String::from("user3@domain.com"),
username: String::from("user3"),
..user2 // übernimmt die Werte von 'active' und 'sign_in_count' von user2
};
println!("User 3 Anmeldeanzahl: {}", user3.sign_in_count);
}
// Funktion, die eine User-Instanz zurückgibt
fn erstelle_user(email: String, username: String) -> User {
User {
email, // Feld-Initialisierungs-Kurzschreibweise: wenn Parametername mit Feldname übereinstimmt
username,
active: true,
sign_in_count: 1,
}
}
Rust unterstützt auch Tupel-Structs, die benannte Tupel sind (z. B. struct Color(i32, i32, i32);
), und Unit-ähnliche Structs, die keine Felder haben und nützlich sind, wenn man einen Trait für einen Typ implementieren muss, aber keine Daten speichern muss (z. B. struct AlwaysEqual;
).
Enums (Enumerationen) ermöglichen es dir, einen Typ durch Aufzählung seiner möglichen *Varianten zu definieren. Ein Enum-Wert kann nur eine seiner möglichen Varianten sein.
// Einfaches Enum zur Definition von IP-Adress-Arten
enum IpAddrKind {
V4, // Variante 1
V6, // Variante 2
}
// Enum-Varianten können auch zugehörige Daten enthalten
enum IpAddr {
V4(u8, u8, u8, u8), // V4-Variante enthält vier u8-Werte
V6(String), // V6-Variante enthält einen String
}
// Ein sehr häufiges und wichtiges Enum in Rusts Standardbibliothek: Option<T>
// Es kodiert das Konzept eines Wertes, der vorhanden oder abwesend sein kann.
// enum Option<T> {
// Some(T), // Repräsentiert das Vorhandensein eines Wertes vom Typ T
// None, // Repräsentiert das Fehlen eines Wertes
// }
// Ein weiteres entscheidendes Standardbibliotheks-Enum: Result<T, E>
// Wird für Operationen verwendet, die erfolgreich sein (Ok) oder fehlschlagen (Err) können.
// enum Result<T, E> {
// Ok(T), // Repräsentiert Erfolg, enthält einen Wert vom Typ T
// Err(E), // Repräsentiert Fehlschlag, enthält einen Fehlerwert vom Typ E
// }
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// Instanzen des IpAddr-Enums mit zugehörigen Daten erstellen
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
// Beispiel mit Option<T>
let some_number: Option<i32> = Some(5);
let no_number: Option<i32> = None;
// Der 'match'-Kontrollflussoperator ist perfekt für die Arbeit mit Enums.
// Er ermöglicht es, unterschiedlichen Code basierend auf der Enum-Variante auszuführen.
match some_number {
Some(i) => println!("Habe eine Zahl bekommen: {}", i), // Wenn es Some ist, binde den inneren Wert an i
None => println!("Habe nichts bekommen."), // Wenn es None ist
}
// 'match' muss erschöpfend sein: Man muss alle möglichen Varianten behandeln.
// Der Unterstrich '_' kann als Wildcard-Muster verwendet werden, um alle Varianten
// abzufangen, die nicht explizit aufgeführt sind.
}
Option<T>
und Result<T, E>
sind zentral für Rusts Ansatz, potenziell fehlende Werte und Fehler robust zu behandeln.
Cargo ist ein unverzichtbarer Teil des Rust-Ökosystems, der den Prozess des Bauens, Testens und Verwaltens von Rust-Projekten rationalisiert. Es ist eine der Funktionen, die Entwickler oft loben.
Cargo.toml
: Dies ist die **Manifest*-Datei für dein Rust-Projekt. Sie ist im TOML (Tom's Obvious, Minimal Language) Format geschrieben. Sie enthält wesentliche Metadaten über dein Projekt (wie Name, Version und Autoren) und, entscheidend, listet seine *Abhängigkeiten (Dependencies) auf (andere externe Crates, von denen dein Projekt abhängt).
[package]
name = "mein_projekt"
version = "0.1.0"
edition = "2021" # Gibt die zu verwendende Rust Edition an (beeinflusst Sprachmerkmale)
# Abhängigkeiten werden unten aufgeführt
[dependencies]
# Beispiel: Füge die 'rand'-Crate für Zufallszahlengenerierung hinzu
# rand = "0.8.5"
# Beim Bauen wird Cargo 'rand' und dessen Abhängigkeiten herunterladen und kompilieren.
cargo new <projektname>
: Erstellt eine neue Projektstruktur für eine binäre (ausführbare) Anwendung.cargo new --lib <bibliotheksname>
: Erstellt eine neue Projektstruktur für eine Bibliothek (Crate, die zur Verwendung durch andere Programme gedacht ist).cargo build
: Kompiliert dein Projekt und seine Abhängigkeiten. Standardmäßig erstellt es einen nicht optimierten **Debug*-Build. Die Ausgabe wird im Verzeichnis target/debug/
abgelegt.cargo build --release
: Kompiliert dein Projekt mit aktivierten Optimierungen, geeignet für die Verteilung oder Leistungstests. Die Ausgabe wird im Verzeichnis target/release/
abgelegt.cargo run
: Kompiliert (falls nötig) und führt dein binäres Projekt aus.cargo check
: Überprüft deinen Code schnell auf Kompilierfehler, ohne tatsächlich die endgültige ausführbare Datei zu erzeugen. Dies ist typischerweise viel schneller als cargo build
und nützlich während der Entwicklung für schnelles Feedback.cargo test
: Führt alle in deinem Projekt definierten Tests aus (normalerweise im src
-Verzeichnis oder in einem separaten tests
-Verzeichnis).Wenn du eine Abhängigkeit zu deiner Cargo.toml
-Datei hinzufügst und dann einen Befehl wie cargo build
oder cargo run
ausführst, übernimmt Cargo automatisch das Herunterladen der benötigten Crate (und deren Abhängigkeiten) aus dem zentralen Repository crates.io und kompiliert alles zusammen.
Dieser Einstiegsleitfaden hat die absoluten Grundlagen behandelt, um dich auf den Weg zu bringen. Die Rust-Sprache bietet viele weitere mächtige Funktionen, die du erkunden kannst, während du Fortschritte machst:
Result
-Enums, Fehlerweitergabe mit dem ?
-Operator und Definition eigener Fehlertypen.Vec<T>
(dynamische Arrays/Vektoren), HashMap<K, V>
(Hash-Maps), HashSet<T>
, etc.<T>
).'a
) erfordert.Mutex
und Arc
und Rusts mächtiger async
/await
-Syntax für asynchrone Programmierung.Rust präsentiert ein einzigartiges und überzeugendes Angebot: die Rohleistung, die man von Low-Level-Sprachen wie C++ erwartet, kombiniert mit starken Sicherheitsgarantien zur Kompilierzeit, die ganze Klassen häufiger Fehler eliminieren, insbesondere im Bereich Speicherverwaltung und Nebenläufigkeit. Während die Lernkurve das Verinnerlichen neuer Konzepte wie Ownership und Borrowing (und das Hinhören auf den manchmal strengen Compiler) beinhaltet, ist die Belohnung Software, die hochgradig zuverlässig, effizient und oft langfristig einfacher zu warten ist. Mit seinen exzellenten Werkzeugen durch Cargo und einer unterstützenden Community ist Rust eine lohnende Sprache zum Lernen.
Fang klein an, experimentiere mit Cargo, nimm die hilfreichen (wenn auch manchmal ausführlichen) Fehlermeldungen des Compilers als Führung an, und du wirst auf dem besten Weg sein, diese mächtige und immer beliebter werdende Sprache zu meistern. Viel Spaß beim Rusten!