Cargo und Crates in Rust
Auf der Seite Erste Schritte mit Rust hast du cargo new und cargo run benutzt. Das war erst der Anfang. Cargo ist das Build-Werkzeug von Rust und sein Paketmanager. Es kompiliert deinen Code, führt deine Tests aus, formatiert deine Dateien und lädt Bibliotheken herunter, die andere geschrieben haben.
Diese Bibliotheken heißen Crates (wörtlich „Kisten“). Stell dir Crates wie Apps in einem App Store vor: Jemand hat ein Problem schon gelöst (JSON lesen, Webanfragen stellen, Datumsangaben verarbeiten), und du kannst diese Lösung mit einem einzigen Befehl in dein Projekt holen. Auf dieser Seite siehst du, wie das funktioniert und wie du ein gutes Crate auswählst.
Crates und Pakete
Abschnitt betitelt „Crates und Pakete“Zwei Begriffe begegnen dir ständig:
- Ein Crate ist eine Einheit aus Rust-Code, die der Compiler übersetzt. Es ist entweder ein Binary (ein Programm, das du ausführen kannst, beginnend bei
src/main.rs) oder eine Library (Bibliothek: Code, den andere Programme benutzen, beginnend beisrc/lib.rs). - Ein Paket (Package) ist das, was
cargo newanlegt: ein Ordner mit einer DateiCargo.tomlund einem oder mehreren Crates darin.
Im Alltag sagen die meisten für beides „Crate“. Wenn jemand sagt „Nimm das Crate serde“, ist gemeint: „Füge das Paket serde von crates.io zu deinem Projekt hinzu.“
Was in Cargo.toml steht
Abschnitt betitelt „Was in Cargo.toml steht“Jedes Paket hat eine Datei Cargo.toml. Sie ist in TOML geschrieben, einem einfachen Format für Einstellungen. So sieht sie nach cargo new hello_rust aus:
[package]name = "hello_rust"version = "0.1.0"edition = "2024"
[dependencies][package]beschreibt dein Projekt.nameundversionwürden andere sehen, wenn du es veröffentlichst.editionlegt fest, welche Edition der Sprache Rust benutzt wird. Dank Editionen (2018, 2021, 2024) kann Rust sich weiterentwickeln, ohne alten Code kaputtzumachen. Lass einfach stehen, wascargo neweingetragen hat.[dependencies]listet die Crates auf, die dein Code benutzt (die Abhängigkeiten). Am Anfang ist der Abschnitt leer.
Wenn ein Projekt wächst, kommen oft weitere Abschnitte dazu:
[dependencies]serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"
[dev-dependencies]# wird nur von Tests und Beispielen benutzt, nicht vom eigentlichen Programmpretty_assertions = "1.4"
[profile.release]# Einstellungen für `cargo build --release`opt-level = 3Eine Abhängigkeit mit cargo add hinzufügen
Abschnitt betitelt „Eine Abhängigkeit mit cargo add hinzufügen“Du kannst [dependencies] von Hand bearbeiten, aber am einfachsten geht es mit cargo add. Der Befehl sucht die neueste Version auf crates.io und schreibt die passende Zeile für dich:
cargo add serde_jsonManche Crates haben optionale Teile, die Features heißen. Sie sind standardmäßig ausgeschaltet, damit dein Build klein bleibt. Mit --features schaltest du sie ein:
cargo add serde --features deriveNach diesen beiden Befehlen steht in deiner Cargo.toml ungefähr Folgendes:
[dependencies]serde = { version = "1.0.228", features = ["derive"] }serde_json = "1.0.145"Jetzt kannst du das Crate in deinem Code benutzen. Beim nächsten cargo run lädt Cargo es herunter, kompiliert es einmal und bindet es in dein Programm ein:
use serde_json::json;
fn main() { let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab); println!("Name: {}", crab["name"]);}Ausgabe:
{"legs":10,"name":"Ferris"}Name: "Ferris"Die Anführungszeichen um "Ferris" erscheinen, weil crab["name"] immer noch ein JSON-Wert ist und kein einfacher Rust-String.
Der Rust Playground enthält die beliebtesten Crates schon, deshalb läuft das Beispiel dort auch ohne cargo add. Auf deinem eigenen Rechner brauchst du vorher cargo add serde_json. Mehr über dieses Crate lernst du auf der Seite JSON mit Serde.
Ein Crate, das du nicht mehr brauchst, entfernst du mit cargo remove serde_json.
Versionsangaben und Cargo.lock
Abschnitt betitelt „Versionsangaben und Cargo.lock“Wenn du serde_json = "1.0" schreibst, verlangst du nicht genau die Version 1.0. Du sagst damit: „irgendeine Version, die mit 1.0 kompatibel ist“. Cargo folgt der semantischen Versionierung (SemVer): Eine Version hat die Form MAJOR.MINOR.PATCH, und nur eine Änderung der ersten Zahl (Major) darf deinen Code kaputtmachen.
| Du schreibst | Cargo darf nehmen | Warum |
|---|---|---|
"1.0" (dasselbe wie "^1.0") |
>=1.0.0 und <2.0.0 |
alle 1.x-Versionen sollten kompatibel sein |
"1.4.2" |
>=1.4.2 und <2.0.0 |
mindestens 1.4.2, aber weiterhin 1.x |
"0.4" |
>=0.4.0 und <0.5.0 |
vor 1.0 gilt schon eine Änderung der Minor-Zahl als inkompatibel |
"~1.4" |
>=1.4.0 und <1.5.0 |
nur Patch-Updates |
"=1.4.2" |
genau 1.4.2 |
wird selten gebraucht |
Wer entscheidet also über die genaue Version? Das ist die Aufgabe von Cargo.lock. Beim ersten Build sucht Cargo die neuesten Versionen heraus, die zu deinen Angaben passen, und schreibt sie in Cargo.lock. Jeder spätere Build benutzt genau dieselben Versionen – auf deinem Rechner und auf allen anderen.
- Bearbeite
Cargo.locknicht von Hand. Darum kümmert sich Cargo. - Committe die Datei in Git, vor allem bei Programmen. Dann bauen dein Team und deine CI genau das, was du gebaut hast.
cargo updatewechselt zu den neuesten Versionen, die noch zuCargo.tomlpassen, und schreibtCargo.lockneu. Dabei springt es nie auf eine neue Major-Version. Dafür änderst duCargo.tomlselbst.
Ein gutes Crate finden
Abschnitt betitelt „Ein gutes Crate finden“Drei Websites wirst du immer wieder benutzen (alle auf Englisch):
- crates.io ist das offizielle Verzeichnis. Jedes öffentliche Crate liegt hier. Suche zum Beispiel nach „csv“ oder „date“.
- docs.rs stellt die Dokumentation jedes Crates bereit, automatisch erzeugt. Zum Beispiel docs.rs/serde_json.
- lib.rs ist ein inoffizielles, schnelleres Verzeichnis, das Crates nach Kategorien ordnet. Ideal zum Stöbern.
Oft lösen mehrere Crates dasselbe Problem. Wie wählst du also aus? Bevor du eines hinzufügst, nimm dir eine Minute und prüfe:
- Downloads. Beliebte Crates haben Millionen Downloads. Das heißt: Viele Leute haben die Fehler schon vor dir gefunden und behoben.
- Letzte Updates. Schau dir das Datum der neuesten Version an. Ein Crate, das sich seit Jahren nicht geändert hat, kann trotzdem in Ordnung sein (manche sind einfach „fertig“). Prüfe aber, ob sein Repository nicht voller unbeantworteter Issues ist.
- Dokumentation. Öffne die Seite auf docs.rs. Gute Crates haben eine klare Startseite mit einem Beispiel, das du kopieren kannst.
- Version 1.0 oder höher. Kein Muss, aber es zeigt dir, dass der Autor eine stabile API verspricht.
- Wenige Abhängigkeiten. Jedes Crate bringt seine eigenen Abhängigkeiten mit. Je kleiner, desto leichter kannst du ihm vertrauen.
Wenn du unsicher bist, suche nach „rust“ plus deinem Problem und schau, welches Crate die Community immer wieder empfiehlt.
Die wichtigsten Cargo-Befehle
Abschnitt betitelt „Die wichtigsten Cargo-Befehle“Du musst sie nicht auswendig lernen. Behalte diese Tabelle in der Nähe, dann lernst du sie beim Benutzen:
| Befehl | Was er macht |
|---|---|
cargo new my_app |
Legt ein neues Binary-Projekt an (mit --lib eine Library) |
cargo build |
Kompiliert nach target/debug/ |
cargo run |
Kompiliert und startet das Programm (Argumente nach --: cargo run -- input.txt) |
cargo check |
Sucht nach Fehlern, ohne ein Programm zu erzeugen. Viel schneller als build |
cargo test |
Führt deine Tests aus (siehe Testen in Rust) |
cargo fmt |
Formatiert deinen ganzen Code im Standardstil |
cargo clippy |
Startet den Linter. Er schlägt einfacheren, idiomatischeren Code vor |
cargo doc --open |
Erzeugt HTML-Dokumentation für dein Projekt und alle Abhängigkeiten und öffnet sie |
cargo update |
Aktualisiert Cargo.lock auf die neuesten kompatiblen Versionen |
cargo build --release |
Kompiliert ein optimiertes Programm nach target/release/ |
Eine gute Gewohnheit: Führe beim Schreiben oft cargo check aus und vor jedem Commit cargo fmt und cargo clippy. Auch cargo doc --open ist ein Geheimtipp: Du bekommst die Dokumentation für genau die Crate-Versionen, die du benutzt – und das sogar offline.
Debug- und Release-Builds
Abschnitt betitelt „Debug- und Release-Builds“Standardmäßig erstellt Cargo einen Debug-Build. Er kompiliert schnell und behält zusätzliche Sicherheitsprüfungen, zum Beispiel einen Panic, wenn eine Ganzzahl überläuft. Während der Entwicklung ist das perfekt.
Ein Release-Build (cargo build --release oder cargo run --release) braucht länger zum Kompilieren, läuft aber viel schneller – oft zehnmal so schnell oder mehr. Benutze ihn, wenn du dein Programm auslieferst oder seine Geschwindigkeit misst.
Dein Code kann sogar erkennen, welche Art von Build gerade läuft:
fn main() { if cfg!(debug_assertions) { println!("Debug-Build: zusätzliche Prüfungen sind aktiv"); } else { println!("Release-Build: auf Geschwindigkeit optimiert"); }}Ausgabe:
Debug-Build: zusätzliche Prüfungen sind aktivIm Playground kannst du im Menü von „Debug“ auf „Release“ umschalten und das Programm noch einmal starten. Dann siehst du die andere Zeile.
Binary- und Library-Crate zusammen
Abschnitt betitelt „Binary- und Library-Crate zusammen“Ein Paket kann gleichzeitig eine Library und ein Binary enthalten. Diese Aufteilung ist sehr verbreitet: Deine eigentliche Logik steht in src/lib.rs, und src/main.rs bleibt klein und ruft sie nur auf:
greeter/├── Cargo.toml└── src/ ├── lib.rs # das Library-Crate: wiederverwendbare Logik └── main.rs # das Binary-Crate: das Programm, das du startestpub fn greet(name: &str) -> String { format!("Hallo, {name}!")}fn main() { // die Library heißt wie das Paket println!("{}", greeter::greet("Ferris"));}Warum diese Aufteilung? Code in lib.rs lässt sich leicht testen, und andere Programme (oder ein zweites Binary) können ihn wiederverwenden. Der Playground kennt nur eine Datei, aber mit einem pub mod bekommst du ein ähnliches Gefühl – wie auf der Seite Module in Rust:
mod greeter { pub fn greet(name: &str) -> String { format!("Hallo, {name}!") }}
fn main() { println!("{}", greeter::greet("Ferris"));}Ausgabe:
Hallo, Ferris!Ein kurzer Blick auf Workspaces
Abschnitt betitelt „Ein kurzer Blick auf Workspaces“Wenn ein Projekt zu mehreren zusammengehörigen Paketen heranwächst (zum Beispiel eine Kommandozeilen-App, ein Webserver und eine gemeinsame Library), kannst du sie in einem Workspace (Arbeitsbereich) zusammenfassen. Sie teilen sich eine Cargo.lock und einen Ordner target/. So wird alles gemeinsam gebaut und benutzt dieselben Versionen der Abhängigkeiten.
Die oberste Cargo.toml listet die Mitglieder auf:
[workspace]members = ["app", "core"]resolver = "3"my_project/├── Cargo.toml # die Workspace-Datei von oben├── Cargo.lock # gemeinsam für alle Mitglieder├── app/ # ein Binary-Paket└── core/ # ein Library-PaketDann baut cargo build im obersten Ordner alles, und cargo run -p app startet ein einzelnes Mitglied. Für deine ersten Projekte brauchst du das nicht. Es ist aber gut, es wiederzuerkennen, wenn du den Code anderer liest.
Häufige Fehler
Abschnitt betitelt „Häufige Fehler“Ein Crate benutzen, ohne es hinzuzufügen. Wenn du use serde_json::json; schreibst, aber cargo add serde_json vergessen hast, findet der Compiler das Crate nicht:
error[E0432]: unresolved import `serde_json` --> src/main.rs:1:5 |1 | use serde_json::json; | ^^^^^^^^^^ use of unresolved module or unlinked crate `serde_json` | = help: you might be missing a crate named `serde_json`Der Compiler sagt hier: Der Import serde_json lässt sich nicht auflösen, und vermutlich fehlt dir ein Crate mit diesem Namen. Die Lösung: Führe cargo add serde_json aus. Prüfe auch die Schreibweise: Crate-Namen enthalten - oder _, und im Code wird aus einem - immer ein _.
Ein Feature vergessen. Wenn #[derive(Serialize)] meldet, dass das Derive-Makro nicht gefunden wird, hast du serde wahrscheinlich ohne --features derive hinzugefügt. Führe cargo add serde --features derive noch einmal aus. Der Befehl aktualisiert die vorhandene Zeile.
Geschwindigkeit mit einem Debug-Build messen. Wenn dir dein Programm langsam vorkommt, probiere zuerst cargo run --release, bevor du irgendetwas am Code änderst.
Übungen
Abschnitt betitelt „Übungen“1. Ein Crate im Playground benutzen
Abschnitt betitelt „1. Ein Crate im Playground benutzen“Beschreib mit dem Makro json! aus serde_json ein Crate mit drei Keys: downloads (eine Zahl), name und version. Gib den ganzen Wert aus und danach nur die Version.
Tipp: Übernimm den Aufbau des crab-Beispiels aus Eine Abhängigkeit mit cargo add hinzufügen. Auf deinem eigenen Rechner führst du zuerst cargo add serde_json aus; im Playground ist es schon vorhanden.
Lösung anzeigen
use serde_json::json;
fn main() { let krate = json!({ "downloads": 500, "name": "serde_json", "version": "1.0" });
println!("{}", krate); println!("Version: {}", krate["version"]);}Ausgabe:
{"downloads":500,"name":"serde_json","version":"1.0"}Version: "1.0"Die Variable heißt krate, weil crate in Rust ein reserviertes Wort ist. Diese Schreibweise ist ein beliebter Insider-Witz in Rust-Code. Wie vorhin wird die Version mit Anführungszeichen ausgegeben, weil sie immer noch ein JSON-Wert ist. Wie du reinen Text herausbekommst, zeigt die Seite JSON mit Serde.
2. Meldungen nur in Debug-Builds
Abschnitt betitelt „2. Meldungen nur in Debug-Builds“Schreib eine Funktion debug_log(msg: &str), die [debug] und danach die Nachricht ausgibt – aber nur in einem Debug-Build. Benutze sie in main rund um eine kleine Rechnung und gib das Ergebnis ganz normal aus.
Tipp: Pack das println! in if cfg!(debug_assertions) { ... }, wie in Debug- und Release-Builds.
Lösung anzeigen
fn debug_log(msg: &str) { if cfg!(debug_assertions) { println!("[debug] {msg}"); }}
fn main() { debug_log("Rechnung startet"); let mut total = 0; for n in 1..=10 { total += n; } debug_log("Rechnung fertig");
println!("Die Summe von 1 bis 10 ist {total}");}Ausgabe:
[debug] Rechnung startet[debug] Rechnung fertigDie Summe von 1 bis 10 ist 55Stell den Playground auf „Release“ um (oder nimm lokal cargo run --release) und starte das Programm noch einmal: Dann erscheint nur die letzte Zeile. Für echte Projekte zeigt dir die Seite Logging Crates, die das richtig machen, mit Stufen wie debug und info.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Cargo baut, startet, testet und formatiert deinen Code und lädt Crates von crates.io herunter.
- In
Cargo.tomlsteht, was du dir wünschst.Cargo.lockhält fest, welche genauen Versionen du bekommen hast. Committe beide. cargo add name --features xfügt eine Abhängigkeit hinzu."1.0"bedeutet „irgendeine kompatible 1.x-Version“.- Beurteile ein Crate nach Downloads, letzten Updates und guter Dokumentation auf docs.rs.
- Benutze
cargo checkbeim Schreiben,cargo fmtundcargo clippyvor dem Commit und--releasefür Geschwindigkeit. - Lege wiederverwendbare Logik in
src/lib.rsab und haltesrc/main.rsklein.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Was bedeutet serde_json = "1.0" in Cargo.toml?
2. Welche Datei hält die genauen Versionen aller Abhängigkeiten fest, die dein Build benutzt hat?
3. Du willst deinen Code so schnell wie möglich auf Fehler prüfen, ohne ein Programm zu erzeugen. Welcher Befehl passt am besten?
4. Wie fügst du serde mit eingeschaltetem Feature derive hinzu?
Punkte: 0 / 4
Weiterlesen
Abschnitt betitelt „Weiterlesen“- The Cargo Book (auf Englisch): die offizielle Anleitung und Referenz für alles, was Cargo kann.
- Specifying Dependencies – The Cargo Book (auf Englisch): alle Arten, eine Versionsangabe zu schreiben, dazu Abhängigkeiten aus Git und von lokalen Pfaden.
cargo add– The Cargo Book (auf Englisch): alle Optionen voncargo add, etwa--dev,--featuresund--no-default-features.- Mehr über Cargo und Crates.io – Das Rust-Buch: Release-Profile, ein eigenes Crate veröffentlichen und Werkzeuge mit
cargo installinstallieren. - Cargo-Arbeitsbereiche – Das Rust-Buch: ein Workspace Schritt für Schritt, mit einem Binary und zwei Bibliotheken.