Bevor wir anfangen
Stell dir vor, wir sitzen nebeneinander an einer Werkbank. Du hast die Tastatur vor dir, ich sitze daneben. Ich sage Dinge wie: „Probier mal …“, „Warte, schau dir die Meldung genau an …“ und „Jetzt bauen wir das aus.“
So ist dieses Buch gedacht. Du bekommst keine fertigen Programme zum Abtippen. Wir fangen jedes Mal mit fast nichts an, lassen es laufen, verändern etwas und schauen, was passiert. Das Programm wächst Schritt für Schritt, und du siehst bei jedem Schritt, warum es so wird, wie es wird.
Für wen ist dieses Buch?
Für dich, wenn du noch nie programmiert hast. Oder wenn du es schon einmal versucht hast und irgendwo hängen geblieben bist. Du brauchst keine Vorkenntnisse. Wenn du weißt, wie man eine Datei speichert und ein Programm startet, kannst du loslegen. Alles andere erkläre ich dir, und zwar langsam. Wenn dir etwas zu ausführlich vorkommt, überspring es einfach. Zu knapp ist für Einsteiger schlimmer als zu ausführlich.
Wenn du schon in einer anderen Sprache programmiert hast, wirst du manches schneller durchblättern. Lass dich nicht täuschen: Rust funktioniert in einigen Dingen grundlegend anders als die meisten Sprachen. Kapitel, die du zu kennen glaubst, verraten oft einen Unterschied im Detail.
Warum Rust?
Programmiersprachen sind Werkzeuge, und jedes Werkzeug hat Stärken. Rust wurde gebaut, damit man schnelle Programme schreiben kann, ohne bei der Sicherheit zu schummeln. Viele schnelle Sprachen lassen dich Fehler machen, die erst beim Ausführen auffallen: Abstürze, kaputte Daten, Sicherheitslücken. Rust verlangt dafür, dass du dem Compiler, dem Übersetzer deines Programms, genau erklärst, was du vorhast. Dafür findet er viele Fehler, bevor das Programm überhaupt läuft.
Das hat einen Preis, und ich verspreche dir, ehrlich darüber zu sprechen: Rust ist am Anfang strenger und anstrengender als andere Sprachen. Du wirst dich manchmal über den Compiler ärgern. Er will meistens etwas Vernünftiges, und seine Meldungen sind für Menschen geschrieben. Dieses Buch bringt dir bei, sie zu lesen.
Was bauen wir?
Wir lernen die Sprache von Grund auf und bauen währenddessen zwei Dinge: kleine Programme mit Fenstern (mit der Bibliothek iced, sprich „eisd“) und das Rezeptbuch, ein Kochbuch mit Vorratsschrank, und ein kleines Spiel, Küchenstress. Beides hält die Theorie in der Praxis fest. Am Ende hast du ein Programm, das du wirklich benutzen kannst, und du verstehst die Sprache so, dass du alleine weiterkommst.
Wie arbeiten wir?
Jedes Kapitel ist in kleine Schritte unterteilt. Damit du dich zurechtfindest, gibt es immer wiederkehrende Abschnitte:
- An der WerkbankHier bauen wir gemeinsam. Du tippst mit.
- HinschauenWir bleiben kurz stehen und schauen uns an, was gerade passiert ist.
- StolperstelleWir provozieren absichtlich einen Fehler. Fehlermeldungen sind in Rust keine Strafe, sondern ein Gespräch mit dem Compiler.
- Jetzt duEine kleine Aufgabe, die du allein probierst, bevor wir weitergehen. Die Lösung steht im Anhang.
- Ehrlich gesagtHier sage ich dir, wo Rust unbequem ist, und warum.
- Ins WerkzeugfachDie Kurzfassung zum Merken. Was hier steht, habe ich dir auch noch einmal in das Cheatsheet im Anhang übertragen.
Eine Bitte noch: Tippe den Code selbst ab, auch wenn es nur drei Zeilen sind. Deine Finger lernen mit, und du machst Tippfehler. An denen lernst du, wie der Compiler mit dir spricht. Kopieren ist erlaubt, aber es ist wie Schwimmen lernen, indem man anderen zuschaut.
Alle Beispiele wurden mit Rust 1.99 und iced 0.14 geprüft, und jede Fehlermeldung in diesem Buch stammt vom echten Compiler. Neuere Versionen funktionieren meist genauso, aber Meldungen können sich im Wortlaut etwas ändern.
Ein Wort zu den Fehlern
Du wirst Fehler machen. Wirklich. Jeder Programmierer macht jeden Tag welche, auch nach 20 Jahren. Der Unterschied zwischen Anfängern und Profis ist nicht, dass Profis weniger Fehler machen, sondern dass sie keine Angst vor ihnen haben. Rust hat dafür einen riesigen Vorteil: Seine Fehlermeldungen sind für Menschen geschrieben und schlagen oft schon die Lösung vor. Lies sie, statt wegzuklicken.
Kapitel 2
Hallo, Rust!
Jetzt wird es ernst, aber nicht schwer. Wir lassen den Computer zum ersten Mal etwas für uns tun.
Ein Projekt anlegen
Ein Programm besteht selten nur aus einer Datei. Rust-Programme leben in einem Projekt, das ist ein Ordner mit ein paar Dateien, die zusammengehören. Cargo legt ihn für uns an.
An der Werkbank
Wechsle im Terminal in deinen Arbeitsordner werkstatt und lege das Projekt an:
$ cargo new hallo_rust
$ cd hallo_rustCargo antwortet:
Creating binary (application) `hallo_rust` package„Binary“ heißt hier: ein Projekt, das ein startbares Programm ergibt, im Gegensatz zu einer Bibliothek. Die erste Zeile erzeugt einen neuen Ordner hallo_rust samt Inhalt. Die zweite wechselt hinein.
Was hat Cargo uns hingestellt?
Öffne den Ordner hallo_rust in VS Code (im Menü: Datei, Ordner öffnen). Du siehst:
Cargo.toml: Der „Steckbrief“ deines Projekts. Schauen wir hinein:
[package]
name = "hallo_rust"
version = "0.1.0"
edition = "2024"
[dependencies]
[package]beschreibt das Projekt: Name, Version, und die Edition der Sprache.[dependencies]ist die Liste der Abhängigkeiten, also der fertigen Bibliotheken, die du benutzt. Sie ist noch leer. Im letzten Kapitel dieses Teils, Kapitel 16, füllen wir sie zum ersten Mal.- Das Dateiformat heißt TOML. Es besteht aus Abschnitten in eckigen Klammern und Zeilen
name = wert. src/main.rs: Dein Quellcode.srcsteht für „source“, also Quelle. Die Endung.rsbedeutet Rust..gitignore: Eine Hilfsdatei für das Versionsverwaltungsprogramm Git. Sie sagt, welche Dateien Git ignorieren soll. Du kannst sie ignorieren.
Später kommen zwei weitere Dinge dazu, wenn du das Projekt baust: ein Ordner target, in den Cargo alle Zwischen- und Endergebnisse legt, und eine Datei Cargo.lock, die genau festhält, welche Versionen von Bibliotheken benutzt wurden. Beide musst du nicht anfassen.
Öffne src/main.rs. Cargo hat sie schon befüllt:
fn main() {
println!("Hello, world!");
}
Drei Zeilen. Du hast noch nichts geschrieben und besitzt bereits ein lauffähiges Programm.
Starten
$ cargo runCargo ruft den Compiler auf, übersetzt dein Programm und startet es. Du siehst etwa:
Compiling hallo_rust v0.1.0 (...)
Finished `dev` profile [unoptimized + debuginfo]
target(s) in 0.17s
Running `target/debug/hallo_rust`
Hello, world!Die letzte Zeile ist die Ausgabe deines Programms. Die anderen Zeilen sind Cargos Berichte, was es gerade tut. Lies sie einmal ganz:
Compilingheißt: Der Compiler hat dein Programm übersetzt.Finished dev profileheißt: Fertig, und zwar im Entwicklungsmodus. Dieser Modus übersetzt schnell, schaltet viele Prüfungen ein und optimiert wenig. Im Kapitel 35 lernst du den Gegenpart kennen.Runningnennt die Datei, die gestartet wird:target/debug/hallo_rust. Das ist dein Programm. Du kannst es auch ohne Cargo direkt aus dem Terminal starten:./target/debug/hallo_rust(unter Windowstarget\debug\hallo_rust.exe).
Starte das Programm noch einmal mit cargo run. Diesmal fehlt die Zeile mit „Compiling“: Cargo hat gemerkt, dass sich nichts geändert hat, und hat nichts neu übersetzt.
Drei Befehle, ein Unterschied
cargo runübersetzt (falls nötig) und startet das Programm.cargo buildübersetzt nur.cargo checkprüft nur, ob dein Code gültig ist, ohne ein Programm zu erzeugen. Das ist deutlich schneller und genau das, was man beim Tippen oft braucht. Dein Editor ruft es im Hintergrund selbst auf.
Gelegentlich möchtest du ganz von vorn anfangen: cargo clean löscht den Ordner target. Der nächste Lauf übersetzt alles neu.
Jetzt du: Ändere den Text
Ändere den Text in "Hallo, Werkstatt!", speichere die Datei (Cmd + S oder Strg + S) und starte noch einmal mit cargo run. Siehst du, dass Cargo dein Programm automatisch neu übersetzt hat?
Hinschauen: Was steht da eigentlich?
Gehen wir die drei Zeilen durch, Stück für Stück:
fn main() {fn ist die Abkürzung für „function“, auf Deutsch Funktion. Eine Funktion ist ein benannter Block von Anweisungen, ein kleines Teilprogramm. Diese hier heißt main („Haupt“). Jedes Rust-Programm beginnt in main. Wenn du dein Programm startest, sucht der Computer diese Funktion und führt sie aus.
Die runden Klammern () nach dem Namen sind für Dinge, die man der Funktion mitgeben kann. Bei main brauchen wir nichts, also stehen sie leer da. Die geschweifte Klammer { öffnet den Block, also den Inhalt der Funktion.
println!("Hallo, Werkstatt!");println kommt von „print line“, also „drucke eine Zeile“. Es gibt den Text in Anführungszeichen im Terminal aus und macht dann einen Zeilenumbruch.
Das Ausrufezeichen nach println verrät etwas Besonderes: Das ist ein Makro, keine normale Funktion. Für den Anfang reicht dir: Ein Makro ist ein Werkzeug, das mehr kann als eine Funktion. Zum Beispiel versteht println! Platzhalter in den Anführungszeichen, wie du gleich sehen wirst.
Am Ende der Zeile steht ein Semikolon ;. Es beendet eine Anweisung, so wie ein Punkt einen Satz beendet. Das Semikolon ist in Rust sehr wichtig. In Kapitel 6 lernst du, wann man es auch weglassen kann.
}Die schließende geschweifte Klammer beendet die Funktion.
Stolperstelle
Fehler sind das beste Lernmaterial. Wir machen absichtlich einen. Schreibe eine zweite Zeile dazu und lösche das Semikolon hinter der ersten:
fn main() {
println!("Hallo, Werkstatt!")
println!("Zweite Zeile");
}Starte mit cargo run. Der Compiler schimpft, und zwar so:
error: expected `;`, found `println`
--> src/main.rs:2:34
|
2 | println!("Hallo, Werkstatt!")
| ^ help: add `;` here
3 | println!("Zweite Zeile");
| ------- unexpected tokenÜbersetzt: „Ich habe ein Semikolon erwartet, aber das nächste println gefunden.“ Der Compiler zeigt dir die Zeile, den genauen Ort (Zeile 2, Zeichen 34) und sagt dir sogar, was du tun sollst: add ; here. Setze das Semikolon wieder ein, und alles läuft.
(Hinter der letzten Anweisung einer Funktion darf es übrigens fehlen. Warum, erfährst du in Kapitel 6.)
Mehr aus println! herausholen
println! kann mehr als feste Texte ausgeben. Probiere dieses Programm:
fn main() {
let name = "Rustifer";
let alter = 3;
println!("Hallo, {}!", name);
println!("Hallo, {name}!");
println!("{0} ist {1} Jahre alt, {1} Jahre!", name, alter);
println!("{name:?} und {alter:?}");
println!("Klammern: {{ und }}");
print!("ohne Umbruch, ");
print!("dann weiter\n");
eprintln!("das geht auf den Fehlerkanal");
}
Die Ausgabe:
Hallo, Rustifer!
Hallo, Rustifer!
Rustifer ist 3 Jahre alt, 3 Jahre!
"Rustifer" und 3
Klammern: { und }
ohne Umbruch, dann weiter
das geht auf den FehlerkanalZeile für Zeile:
{}ist ein Platzhalter. Der Wert dafür steht hinter dem Text, durch Komma getrennt. Bei mehreren Platzhaltern der Reihe nach.{name}setzt den Wert der Variablenamedirekt ein. Das ist die modernere und meist lesbarere Schreibweise.letkennst du noch nicht, das erklärt das nächste Kapitel. Es legt einen Namen für einen Wert fest.{0}und{1}sprechen die Werte hinter dem Text nach Nummer an. So kannst du denselben Wert mehrfach einsetzen.{name:?}gibt den Wert in der Debug-Form aus. Bei Text sieht man dadurch die Anführungszeichen. Bei komplizierteren Dingen ist das oft die einzige Möglichkeit, sie anzusehen. Wir benutzen das ständig.{{und}}geben die geschweiften Klammern selbst aus.print!ist wieprintln!, aber ohne Zeilenumbruch.\nist ein Zeilenumbruch.eprintln!gibt auf dem Fehlerkanal aus. Im Terminal sieht man keinen Unterschied. Aber erfahrene Nutzer können Fehler und normale Ausgaben getrennt umleiten. Für Fehlermeldungen isteprintln!die richtige Wahl.
Kommentare
Manchmal willst du etwas in den Code schreiben, das nur für Menschen gedacht ist. Dafür sind Kommentare da. Der Compiler überliest sie:
fn main() {
// Ein Kommentar für Menschen
println!("Hallo"); // auch hinter einer Zeile
/* Ein langer
Kommentar */
println!("Welt");
}
//kommentiert den Rest der Zeile aus./* … */kommentiert alles dazwischen aus, auch über mehrere Zeilen.- Es gibt noch
///für Dokumentationskommentare, die zu einer Funktion gehören. Davon später.
Ein guter Kommentar sagt, warum etwas so ist, nicht was da steht. Was dasteht, sieht man ja.
Jetzt du: Eine Visitenkarte
Schreibe ein Programm, das eine Visitenkarte ausgibt: einen Rahmen aus Strichen, darin Name, Beruf und Ort in je einer Zeile. Verwende dazu mindestens zwei Variablen mit {name} im Text.
Erst selbst probieren, dann hier die Lösung ansehen
fn main() {
let name = "Volker Daehn";
let beruf = "Tischler";
let ort = "Hamburg";
println!("+------------------------+");
println!("| {name}");
println!("| {beruf}");
println!("| {ort}");
println!("+------------------------+");
}Ins Werkzeugfach
- Jedes Rust-Programm startet in der Funktion
fn main(). - Ein Projekt besteht aus
Cargo.toml(Steckbrief) undsrc/main.rs(Quelltext). Der Ordnertargetist Cargos Werkstatt. cargo runübersetzt und startet.cargo checkprüft nur und ist am schnellsten.- Mit
println!("Text {name}")gibst du eine Zeile aus.print!ohne Umbruch,eprintln!auf den Fehlerkanal. {x:?}gibt den Wert in der Debug-Form aus.- Anweisungen enden meist mit einem Semikolon.
- Kommentare:
//bis Zeilenende,/* */für mehrere Zeilen.