Beschreibung
Generelle Rust-Grundlagen und Konzepte lernen. Auch, wie man Cargo bedient.
Ziel
Das jeder der Teilgenommen hat ungefähr weiß wie man Rust benutzt.
Art der Session
Experimentiersession
Nützliches Material
Vorgehen
Voraussetzungen
Lernziel
Zeit und Ort
Teilnehmende
Wer hat Interesse?
- Ja
- Vielleicht
Bereits erledigt
Aufsetzen
- Wie installiert man Rust
Man installiert Rust mitrustup
wie auf der Rustlang-Website spezifiziert, oder alternativ über das Debian-Paket - Man kann wenn man möchte auch auf hier den Rust analyser LSP zu seinem Texteditor hinzufügen
Erste Schritte
Hallo Welt
Um ein Hallo Welt Programm zu erstellen muss man eine Datei mit der .rs
Endung erstellen und diesen Code einfügen:
fn main() {
println!("Hallo, Welt");
}
Die Main-Funktion
fn main() { //snip }
Die main Funktion wird beim Starten den Programms ausgeführt und geladen. Sie ist der Einstiegspunkt ins Programm.
Das println!() Makro
println!("Hallo, Welt");
println! (=print line) ist ein Makro, welches einen formatierten String über den Standard-Output in die Konsole printed. Es ist vergleichbar mit print
in python.
Ein Makro ist ein Stück Code, welches bei Compile-Time expandiert und ausgefüllt wird. Dies macht den Code lesbarer. Ein Makro hat immer ein !
am Ende.
Ausführung des Programmes
Um unser Hallo-Welt-Programm auszuführen müssen wir die Code-Datei compilieren. Dies machen wir einfach mit dem Befehl rustc
(= Rust-Compiler). Dann können wir die binäre Datei, die der Compiler zurück gibt ausführen. Das sieht dann so aus:
rustc filename.rs
./filename
Cargo-Basics
Cargo ist der offizielle Paket-Manager für Rust und das offizielle Build-Tool.
Um mit Cargo ein neues Projekt zu erstellen muss man einfach cargo new <Projektname>
ausführen und Cargo erstellt ein neues VCS-Verzeichnis (standardmäßig mit git), einem src
ordner, wo die Rust-Code-Dateien gespeichert werden, einer Cargo.toml
, welche die Abhängigkeiten und die Aktuelle Paketversion enthält, einer Cargo.lock
in der Eine Versionsgeschichte gespeichert wird und dem target
Verzeichnis, wo Cargo die Ausführbaren Dateien speichert.
Um das Programm mit Cargo zu Compilieren gibt es den einfachen Befehl cargo build
und um das Programm zu Compilieren und auszuführen cargo run
. Außerdem gibt es noch den Befehl cargo clippy
, welcher dir Feedback zu deinem Code gibt und mit schnell überprüft, ob der Code überhaupt compilieren würde.
Außerdem gibt es den cargo doc
(cargo doc --open
) Befehl, welcher eine Dokumentation für den Code anhand von Dokumentierungs-Kommentaren (///
), in denen man Markdown schreiben kann, generiert und mit der --open
Flagge im Browser öffnet.
Mutable und Immutable
Variablen in Rust sind standardmäßig immutable, das heißt sie können nicht nach der Zuweisung geändert werden, dies kann man bei der Deklaration mit dem mut
-Keyword ändern, welches die Variable mutable/änderbar macht.
let a = 12; // immutable i32 a
a += 2; // Fehler, a ist immutabel und kann nicht verändert werden
let mut b = 12; // mutable i32 b
b += 2; // Ok, b ist mutabel und darf verändert werden.
Jedoch kann man immutable Variablen ändern, indem man sie neu zuweist, auch shadowed. Diese Aktion braucht jedoch mehr Leistung, als die Variable zu ändern, deswegen benutzt man mutable Variablen, wenn man weiß, dass sich der Wert ändern wird.
let a = 12;
let a = a + 12; // Ok, da a neu zugewiesen wird.
Normale Variabeltypen
Ganzzahlen
Signed | Größe | Unsigned |
---|---|---|
i8 | 1 byte | u8 |
i16 | 2 byte | u16 |
i32 | 4 byte (standard) | u32 |
i64 | 8 byte | u64 |
i128 | 16 byte | u128 |
isize | Architektur | usize |
Kommazahlen / Floatingpoints
Standard f64
= signed 64 bit Präzision Kommazahl
f32
= signed 32bit
Boolean
Der Boolean wird mit bool
festgelegt und kann true
oder false
sein. Er ist trotzdem 1 Byte groß.
Buchstabe
Der Buchstabentyp akzeptiert einen einzelnen UTF-8 Unicode Buchstaben.
let a: char = 'Z'; // Es müssen einfache Anführungszeichen sein
let b: char = '😻';
Tuple
Ein Tuple ist eine Sammlung von unterschiedlichen Datentypen.
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
Array
Ein Array ist ein Block Arbeitsspeicher mit aufeinander folgenden Daten des selben Typen.
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
// a und b sind effektiv gleich
let c = [1, 1, 1, 1, 1];
let d = [1; 5];
let e = [1i32; 5];
// c, d und e sind effektiv gleich
Wichtig: Wenn man versucht auf ein Element außerhalb eines Arrays zuzugreifen wird das Programm paniken und beendet werden
Sessioncode
use std::io::stdin;
/// Struct for a house with the number of inhabitants and the name of the House
// Sodass man sich die Variable ausgeben lassen kann, fügt standard print Verhalten
// hinzu.
#[derive(Debug)]
struct House {
inhabitants: i32, // 32 bit signed integer
name: String, // string auf dem heap
}
impl House {
/// Creates a new House struct when given a number of inhabitants and
/// a string as the name of the house
fn new(inhabitants: i32, name: String) -> Self {
House { inhabitants, name } // Ausdruck: Gibt ein house mit inhabitants und
// name zurück.
}
}
/// Adds two numbers a and b together
// Funktion, welche 2 32 bit integer, a und b, als Argumente nimmt und einen 32 bit
// Integer, a + b zurück gibt, spezifiziert mit `-> i32`
fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Asks the the question to the user and Returns an OK 32-bit Integer Result or
/// an Err string literal result: "You didn't input an i32"
// Gibt einen Ok() und einen Err() zurück, beides Varianten des Result enums
fn input_int(question: &str) -> Result<i32, &str> {
println!("{question}");
let mut input = String::new();
// Ließt die vom Nutzer eingegebene Zeile ein, durch ein Enter spezifiziert
// und falls das nicht geht, was ein Betriebssystemfehler sein muss,
// wird das Programm mit dem Error "Failed to read line" beendet
stdin().read_line(&mut input).expect("Failed to read line");
// Die Funktionskette input.trim().parse() entfernt den überflüßigen Whitespace,
// wie den Newline-Buchtaben \n, vom String Input und versucht den Input in einen
// i32 zu übersetzen, da wir festgelegt haben das unsere Funktion im Fall Ok(),
// einen i32 zurückgibt, also wenn .parse() erfolgreich ist, wird Ok(num)
// zurückgegeben
if let Ok(num) = input.trim().parse() {
Ok(num);
}
// Wenn die .parse nicht erfolgreich ist wird der Fehlertext
// You didn't input an integer zurückgegeben, das zwingt den Code, der die
// Funktion ruft, den möglichen Fehler zu beachten.
else {
Err("You didn't input an integer")
}
}
fn main() {
// a wird ein vom Nutzer eingegebener Wert zugewiesen
// Der Nutzer wird solange etwas eingeben müssen, bis er einen gültigen i32
// eingibt
// loop = while True
let a = loop {
match input_int("Input an integer") {
// Beendet die Schleife und gibt den i32, von der input Funktion, zurück.
Ok(num) => break num,
// Wirft den Fehler weg und startet die Schleife von neuem
Err(_) => continue,
}
};
let b = loop {
// Kurzsyntax für das match oben
if let Ok(num) = input_int("Input a second integer") {
break num;
}
};
let c = add(a, b); // c = a + b Funktion oben
// Formatiert den String, wo die {} Platzhalter für Variablen sind
println!("You put in {a} and {b}\n{a} + {b} = {c}");
if c < 30 {
println!("You failed");
} else {
println!("You won");
}
while c < 30 {
println!("You failed");
break;
}
// Liste von 0 bis 3 inklusive = 4 Wiederholungen, wobei i
// jedes mal neu zugewiesen wird
for i in 0..=3 {
println!("{i}");
}
// Siehe House oben
let house = House::new(12, String::from("Darius"));
// Print house, mit debug Format, da eigener Typ
println!("{:?}", house);
}
Nächsten Schritte
Was passiert als nächstes? Was sind die nächsten Aufgaben?
- Ein Exkurs in C
- generische Datentypen
- Enums
- Burrow-Checker ( Besitztum, Verleih, Lebenszeiten )
- Error Handling
- Option<T>
- Vektoren, Strings und UTF-8
- Hashmaps (Dictionaries)
- Pakete, Crates und Module
- Traits
- Closures
- Iteratoren
- Smart Pointers