Cargo ist in Rust das, was npm + tsc + webpack in der JavaScript-Welt zusammen sind: Paketmanager, Build-System, Test-Runner, Doku-Generator und Publish-Werkzeug in einem. Anders als anderswo musst du dich nicht zwischen konkurrierenden Tools entscheiden — Cargo gehört zur Rust-Toolchain, ist offiziell, stabil und das, womit alle arbeiten. Dieser Artikel führt durch Cargo.toml und Cargo.lock, zeigt die täglich benutzten Kommandos, ordnet die Build-Profile ein und schließt mit den Sonderfeatures, die Cargo von anderen Paketmanagern abhebt.
Was Cargo macht
Drei Hauptaufgaben:
- Build-Orchestrierung. Cargo ruft
rustcmit den richtigen Argumenten auf, parallelisiert Compile-Units, verwaltet inkrementelle Builds und cached Artefakte intarget/. - Dependency-Management. Cargo liest deine Anforderungen aus
Cargo.toml, löst sie über crates.io (oder andere Registries) auf, lädt die Quellen, baut sie und linkt sie in dein Projekt. - Workflow-Werkzeuge. Tests laufen lassen (
cargo test), Dokumentation generieren (cargo doc), Code formatieren (cargo fmt), linten (cargo clippy), zur Registry hochladen (cargo publish).
Das alles über ein einheitliches cargo-Kommando, gegen das es kein zweites populäres Tool gibt — keine Build-Tool-Kriege wie in C++ (CMake vs. Meson vs. Bazel) oder JavaScript (npm vs. yarn vs. pnpm).
Cargo.toml im Detail
Cargo.toml ist das Manifest deines Crates. Es ist eine TOML-Datei (TOML = Tom's Obvious, Minimal Language), strukturiert in Sektionen.
Minimales Manifest
[package]
name = "meine-app"
version = "0.1.0"
edition = "2024"
[dependencies]Drei Pflichtfelder unter [package]:
name— der Crate-Name. Lowercase mit Bindestrichen oder Unterstrichen. Muss auf crates.io eindeutig sein, falls du publizieren willst.version— eine Semver-Versionsnummer. Cargo nimmt Semver wörtlich (mehr dazu im Editionen- und Module-Kapitel).edition— die Sprach-Edition (2015, 2018, 2021 oder 2024).
Erweiterte Package-Felder
[package]
name = "meine-app"
version = "0.1.0"
edition = "2024"
rust-version = "1.78" # MSRV — Minimum Supported Rust Version
authors = ["Max Mustermann <max@example.com>"]
description = "Eine kurze Beschreibung des Crates."
license = "MIT OR Apache-2.0" # SPDX-Identifier
repository = "https://github.com/user/meine-app"
homepage = "https://meine-app.example.com"
documentation = "https://docs.rs/meine-app"
readme = "README.md"
keywords = ["cli", "web"] # Max 5
categories = ["command-line-utilities"] # Aus dem crates.io-VerzeichnisPflicht für ein Publish auf crates.io sind description und license. Der Rest macht den Eintrag auf crates.io und docs.rs reicher.
Dependencies
[dependencies]
# Einfache Version
serde = "1.0"
# Mit Features
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
# Aus einem Git-Repo
my-fork = { git = "https://github.com/user/repo.git", branch = "main" }
# Aus einem lokalen Pfad (für Multi-Crate-Setups)
utils = { path = "../utils" }
# Optional (wird nur aktiv, wenn ein Feature aktiv ist)
chrono = { version = "0.4", optional = true }Versionsangaben folgen einer Caret-Konvention:
"1.0"bedeutet>=1.0.0, <2.0.0— kompatible Updates innerhalb der gleichen Major-Version sind erlaubt."=1.2.3"— exakte Version (selten sinnvoll).">=1.0, <1.5"— bestimmtes Intervall."~1.2.3"— Patch-Level-Updates erlaubt (>=1.2.3, <1.3.0).
Weitere Dependency-Sektionen
# Nur in Tests, Beispielen und Benchmarks verfügbar
[dev-dependencies]
proptest = "1.4"
# Beim Bauen von build.rs verfügbar (Build-Time)
[build-dependencies]
cc = "1.0"dev-dependencies werden nicht als Teil deines Crates publiziert — Benutzer deiner Library bekommen sie nicht. build-dependencies betreffen nur das Build-Skript build.rs, falls dein Crate eines hat.
Features
[features]
default = ["json"]
json = ["dep:serde", "dep:serde_json"]
async = ["dep:tokio"]Features sind Compile-Zeit-Schalter. Benutzer können einzelne Features aktivieren, ohne dass die ungenutzten Dependencies mit eingezogen werden — ein wichtiges Werkzeug für schlanke Builds.
Profile
[profile.release]
opt-level = 3 # Optimierungslevel (0-3, "s", "z")
lto = "fat" # Link-Time Optimization
codegen-units = 1 # Volle Optimierung über alles
strip = true # Symbole entfernen
panic = "abort" # Statt unwind — kleinere BinaryDefaults sind sinnvoll. Anpassungen brauchst du erst, wenn du Binary-Größe oder Laufzeit-Performance gezielt optimierst — siehe das Tooling-Kapitel.
Cargo.lock
Cargo.lock speichert die exakten Versionen aller transitiven Dependencies. Es entsteht automatisch beim ersten cargo build und wird bei jedem cargo update neu geschrieben.
Faustregel:
- Bei Binary-Crates (Anwendungen):
Cargo.lockcommitten. Damit hat jeder, der dein Projekt baut — Teammitglied, CI, Produktions-Server — exakt dieselben Versionen. - Bei Library-Crates:
Cargo.locktypischerweise nicht committen. Benutzer deiner Library entscheiden selbst über Versionen ihrer Dependencies; dein Lock-File ist für sie irrelevant.
Cargo.lock ist nicht für Menschen gemacht. Du bearbeitest ihn nie direkt. Wenn du Versions-Updates willst: cargo update oder cargo update -p <crate> für gezielte Updates.
Die Kern-Befehle
Projekt anlegen
cargo new mein-projekt # Neues Verzeichnis + Git-Init + Binary-Crate
cargo new --lib meine-lib # Library statt Binary
cargo init # Wie cargo new, aber im aktuellen Verzeichniscargo new legt automatisch ein Git-Repo an. Mit --vcs=none lässt es das.
Bauen und Ausführen
cargo check # Schneller Typ-Check, keine Binary
cargo build # Debug-Build
cargo build --release # Release-Build
cargo run # Debug-Build + ausführen
cargo run --release # Release-Build + ausführen
cargo run -- arg1 arg2 # Argumente ans Programm (-- trennt)Dependencies verwalten
cargo add serde # Hinzufügen
cargo add tokio --features "rt-multi-thread,macros"
cargo add --dev proptest # Als dev-dependency
cargo remove serde # Entfernen
cargo update # Alles aktualisieren
cargo update -p tokio # Nur tokio
cargo tree # Dependency-Baum anzeigen
cargo tree --duplicates # Doppelte Versionen findencargo tree ist Gold wert, wenn du mal nachvollziehen willst, warum ein Crate bei dir landet — die Antwort steht oft im transitiven Pfad.
Testen, Dokumentieren, Linten
cargo test # Alle Tests laufen lassen
cargo test --lib # Nur Unit-Tests in src/
cargo test --doc # Nur Doc-Tests
cargo test <name-prefix> # Nur Tests, die mit name-prefix anfangen
cargo doc --open # Doku bauen und im Browser öffnen
cargo doc --no-deps # Nur eigenes Crate, ohne Dependencies
cargo fmt # Code formatieren
cargo fmt -- --check # Nur prüfen (für CI)
cargo clippy # Linten
cargo clippy -- -D warnings # Warnings als Errors (für CI)Publizieren
cargo login # Einmalig mit crates.io-Token einloggen
cargo publish --dry-run # Trockenlauf — packen, aber nicht hochladen
cargo publish # Hochladen
cargo yank --version 0.2.1 # Eine veröffentlichte Version zurückziehenyank löscht eine Version nicht — sie bleibt für bestehende Lock-Files verfügbar, ist nur für neue Projekte nicht mehr auflösbar. Echtes Löschen geht aus gutem Grund nicht.
target/ — der Build-Cache
cargo build legt alles unter target/ ab:
target/
├── debug/
│ ├── meine-app ← Debug-Binary
│ ├── build/
│ ├── deps/ ← .rlib-Dateien aller Dependencies
│ ├── examples/
│ └── incremental/ ← Inkrementelle Compile-Caches
└── release/
├── meine-app ← Release-Binary
└── ...Tipps zum Umgang:
target/kann groß werden (mehrere GB bei großen Projekten). Steht im.gitignore— niemals committen.cargo cleanlöscht alles. Nutze es selten — der Cache ist Gold wert.cargo clean -p <crate>löscht nur Artefakte eines einzelnen Crate.- Für Geld sparen auf der Festplatte: das Tool
cargo-sweepräumt alte Build-Artefakte projektübergreifend auf.
Shared Target-Verzeichnis
Wenn mehrere Projekte sich Dependencies teilen, kannst du einen gemeinsamen Cache über CARGO_TARGET_DIR setzen:
export CARGO_TARGET_DIR=$HOME/.cache/rust-targetVorsicht — gemeinsame Targets können bei parallelen Builds zu File-Lock-Problemen führen. Für lokale Solo-Setups aber spürbarer Speed- und Plattengewinn.
.cargo/config.toml — lokale Cargo-Konfiguration
Cargo liest pro Projekt (oder Home-Verzeichnis) eine config.toml, in der du Verhalten anpassen kannst — Aliase, Target-spezifische Linker-Flags, Build-Voreinstellungen.
# Aliase für häufige Befehle
[alias]
b = "build"
r = "run"
t = "test"
rr = "run --release"
c = "check --all-targets"
# Default-Build-Target
[build]
target = "x86_64-unknown-linux-musl"
# Schnelleres Linking auf Linux mit mold
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]Suchpfade: zuerst ./.cargo/config.toml, dann nach oben Richtung Root, dann ~/.cargo/config.toml. Die spezifischere Konfiguration gewinnt.
Workspaces (Ausblick)
Wenn ein Projekt aus mehreren Crates besteht — z. B. ein Backend, eine geteilte Library und ein CLI — bündelst du sie als Workspace:
[workspace]
members = ["backend", "lib-shared", "cli"]
resolver = "2"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }Alle Member-Crates teilen sich einen target/-Cache und ein gemeinsames Cargo.lock. Im Detail bekommt das Kapitel Module, Crates & Cargo einen eigenen Artikel.
Besonderheiten
cargo add ist neuer, als man denkt.
Bis Rust 1.62 (Mitte 2022) gab es cargo add nur als Third-Party-Tool (cargo-edit). Inzwischen ist es Teil von Cargo selbst. In älteren Anleitungen wird oft noch das manuelle Editieren von Cargo.toml gezeigt — der cargo add-Befehl ist heute der idiomatische Weg, weil er Version, Features und Quellen direkt sauber einsetzt.
cargo install ist kein cargo build.
cargo install <crate> lädt ein Crate, baut es im Release-Profil und legt das Binary unter ~/.cargo/bin/ ab — eine Art globaler Tool-Installer für CLI-Crates wie ripgrep, bat, cargo-watch. Es hat nichts mit Dependencies deines aktuellen Projekts zu tun. Verwechslung sehr häufig bei Einsteigern.
Cargo.lock entsteht auch in Library-Crates.
Auch wenn du Cargo.lock in Libraries nicht commitest, schreibt Cargo lokal trotzdem eins. Es wird beim ersten cargo build/cargo test angelegt, damit auch Library-Entwicklung mit reproduzierbarem Build läuft. Nur eben nicht in Git.
Versionsangabe „1“ ist nicht „1.0“, sondern „1.x.y“ mit x >= 0.
Ein häufiges Missverständnis: "1" in der Dependency-Zeile lässt jede 1.x.y-Version zu, inklusive 1.99.0. Wenn du ^1.2 brauchst, musst du es explizit so schreiben. Die Caret-Logik ist genau definiert — siehe Cargo-Doku zu Specifying Dependencies.
Cargo unterstützt eigene Sub-Befehle.
Jede Binary mit Name cargo-foo im PATH wird automatisch als cargo foo aufrufbar. So funktionieren cargo-watch, cargo-expand, cargo-audit, cargo-dist, cargo-edit. Du kannst eigene Tools genauso einbinden — kein Plugin-System nötig, nur die Naming-Konvention.
Offline-Builds mit --offline und --frozen.
cargo build --offline verhindert jeden Netz-Zugriff. cargo build --frozen erzwingt zusätzlich, dass Cargo.lock exakt zur Lockfile-Erwartung passt — perfekt für reproduzierbare CI-Builds. Beide Flags zusammen genutzt sind die idiomatische Methode, in Air-Gapped-Umgebungen oder Docker-Build-Stages zu arbeiten.
cargo run --example für Beispiel-Programme.
Lege Beispiele unter examples/<name>.rs ab — Cargo behandelt sie als eigenständige Binaries, die du mit cargo run --example <name> startest. Praktisch für Libraries, um Verwendungs-Demos direkt im Repo zu zeigen. Die offiziellen Crates wie serde, tokio oder clap machen das exzessiv.
Du kannst Cargo durch eigene Registry-Mirrors konfigurieren.
In .cargo/config.toml lässt sich ein alternativer Registry-Host eintragen — wichtig für Firmenumgebungen ohne direkten Internetzugriff oder wenn man crates.io aus Performance-Gründen über einen Mirror wie crates.io-sparse-mirror zieht. Seit Rust 1.68 ist das Sparse-Index-Protokoll Default und fast immer schneller als der alte Git-Index.
Weiterführende Ressourcen
Externe Quellen
- The Cargo Book
- The Cargo Book – Manifest Format
- The Cargo Book – Specifying Dependencies
- The Cargo Book – Profiles
- crates.io – Paket-Registry
- docs.rs – Auto-generierte Doku aller crates.io-Crates
- lib.rs – alternativer Crates-Browser