Zum Inhalt springen

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.

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 bei src/lib.rs).
  • Ein Paket (Package) ist das, was cargo new anlegt: ein Ordner mit einer Datei Cargo.toml und 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.“

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. name und version würden andere sehen, wenn du es veröffentlichst.
  • edition legt fest, welche Edition der Sprache Rust benutzt wird. Dank Editionen (2018, 2021, 2024) kann Rust sich weiterentwickeln, ohne alten Code kaputtzumachen. Lass einfach stehen, was cargo new eingetragen 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 Programm
pretty_assertions = "1.4"
[profile.release]
# Einstellungen für `cargo build --release`
opt-level = 3

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:

Terminal window
cargo add serde_json

Manche Crates haben optionale Teile, die Features heißen. Sie sind standardmäßig ausgeschaltet, damit dein Build klein bleibt. Mit --features schaltest du sie ein:

Terminal window
cargo add serde --features derive

Nach 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"
▶ Selbst ausprobieren

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.

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.lock nicht 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 update wechselt zu den neuesten Versionen, die noch zu Cargo.toml passen, und schreibt Cargo.lock neu. Dabei springt es nie auf eine neue Major-Version. Dafür änderst du Cargo.toml selbst.

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:

  1. Downloads. Beliebte Crates haben Millionen Downloads. Das heißt: Viele Leute haben die Fehler schon vor dir gefunden und behoben.
  2. 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.
  3. Dokumentation. Öffne die Seite auf docs.rs. Gute Crates haben eine klare Startseite mit einem Beispiel, das du kopieren kannst.
  4. Version 1.0 oder höher. Kein Muss, aber es zeigt dir, dass der Autor eine stabile API verspricht.
  5. 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.

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.

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 aktiv
▶ Selbst ausprobieren

Im Playground kannst du im Menü von „Debug“ auf „Release“ umschalten und das Programm noch einmal starten. Dann siehst du die andere Zeile.

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 startest
src/lib.rs
pub fn greet(name: &str) -> String {
format!("Hallo, {name}!")
}
src/main.rs
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!
▶ Selbst ausprobieren

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-Paket

Dann 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.

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.

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"
▶ Selbst ausprobieren

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.

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 fertig
Die Summe von 1 bis 10 ist 55
▶ Selbst ausprobieren

Stell 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.

  • Cargo baut, startet, testet und formatiert deinen Code und lädt Crates von crates.io herunter.
  • In Cargo.toml steht, was du dir wünschst. Cargo.lock hält fest, welche genauen Versionen du bekommen hast. Committe beide.
  • cargo add name --features x fü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 check beim Schreiben, cargo fmt und cargo clippy vor dem Commit und --release für Geschwindigkeit.
  • Lege wiederverwendbare Logik in src/lib.rs ab und halte src/main.rs klein.

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