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 rustc mit den richtigen Argumenten auf, parallelisiert Compile-Units, verwaltet inkrementelle Builds und cached Artefakte in target/.
  • 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

Rust Cargo.toml
[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

Rust Cargo.toml (vollständigere Version)
[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-Verzeichnis

Pflicht für ein Publish auf crates.io sind description und license. Der Rest macht den Eintrag auf crates.io und docs.rs reicher.

Dependencies

Rust [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

Rust dev- und build-dependencies
# 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

Rust 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

Rust Profile anpassen
[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 Binary

Defaults 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.lock committen. Damit hat jeder, der dein Projekt baut — Teammitglied, CI, Produktions-Server — exakt dieselben Versionen.
  • Bei Library-Crates: Cargo.lock typischerweise 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

Rust Erstellen
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 Verzeichnis

cargo new legt automatisch ein Git-Repo an. Mit --vcs=none lässt es das.

Bauen und Ausführen

Rust Bauen
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

Rust Dependencies
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 finden

cargo 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

Rust Workflow
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

Rust crates.io
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ückziehen

yank 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:

Rust target/-Struktur
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 clean lö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-sweep rä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:

Rust Shared target
export CARGO_TARGET_DIR=$HOME/.cache/rust-target

Vorsicht — 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.

Rust .cargo/config.toml
# 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:

Rust Cargo.toml (Workspace-Root)
[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

/ Weiter

Zurück zu Grundlagen

Zur Übersicht