Rust-Entwicklung mit Claude Code: Cargo, Ownership, Tests und CLI-Refactoring
Praxisguide zu Rust mit Claude Code: Cargo, Ownership, Tests, fmt, clippy, Fehlerbehandlung und ein kleines CLI.
Mit einer kleinen, prüfbaren Rust-Aufgabe beginnen
Rust wirkt am Anfang streng, weil Ownership, Borrowing, Lifetimes und Fehlerbehandlung mit Result neu sind. Genau diese Strenge ist aber der Nutzen: Der Compiler zwingt dich, gefährliche Zustände früh zu klären. Claude Code hilft in Rust-Projekten am meisten, wenn du es nicht als blinden Codegenerator nutzt, sondern als Partner, der Compilerfehler erklärt, kleine Dateien editiert und anschließend Tests ausführt.
In diesem Artikel bauen wir ein kleines CLI namens tasknote. Es liest tasks.txt mit Zeilen wie [ ] task und [x] task, zeigt eine Zusammenfassung und kann offene Aufgaben ausgeben. Als Quellen verwenden wir die offiziellen Dokumente: Rust Book zu Ownership, Cargo Book zum Projektstart, cargo test, rustfmt, Clippy und Claude Code overview.
Wenn du Claude Code noch nicht sicher nutzt, lies zuerst den Getting-started-Guide. Für Teams gehören Projektregeln in CLAUDE.md best practices und Ausführungsgrenzen in den Permissions Guide.
flowchart LR
Prompt["Ziel und Grenzen"]
Cargo["Kleines Cargo-Projekt"]
Compiler["Ownership-Fehler lesen"]
Tests["Verhalten mit Tests sichern"]
Quality["fmt und clippy"]
Refactor["Kleines Refactoring"]
Prompt --> Cargo --> Compiler --> Tests --> Quality --> Refactor
Cargo-Projekt anlegen
Cargo ist das Standardwerkzeug für Rust-Pakete, Builds, Ausführung, Tests und Dependencies. Definiere vor der Codegenerierung die Prüfkommandos. Dadurch bekommt Claude Code ein klares Ziel.
cargo new tasknote --bin
cd tasknote
cargo run
Das Projekt enthält Cargo.toml und src/main.rs. Aktuelle Projekte können edition = "2024" verwenden, also soll Claude Code das echte Manifest lesen und nicht alte Beispiele kopieren. Für dieses Beispiel nutzen wir nur clap für CLI-Argumente und anyhow für Fehlerkontext.
[package]
name = "tasknote"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
Der erste Prompt sollte nur Design verlangen:
In diesem Cargo-Projekt soll ein CLI `tasknote` entstehen.
Es liest `[ ] task` und `[x] task` aus `tasks.txt`.
Gib zuerst nur das Design für `Cargo.toml`, `src/lib.rs` und `src/main.rs` zurück.
Bearbeite noch keine Dateien. Erkläre Ownership und Fehlerbehandlung.
Diese Pause verhindert schlechte Struktur. In Rust entscheidet die Frage, wer Daten besitzt und wer sie nur ausleiht, über die Einfachheit des Codes.
Ownership und Borrowing gezielt fragen
Ownership bedeutet: Ein Wert hat einen Besitzer. Borrowing bedeutet: Code nutzt einen Wert vorübergehend, ohne ihn zu übernehmen. Lifetimes beschreiben, wie lange eine Referenz gültig ist. Für Einsteiger ist die beste Frage: Wer besitzt die Daten, und wer liest nur?
In tasknote wird der Dateiinhalt als String gelesen. parse_tasks bekommt nur &str, weil es den Text liest. Das Ergebnis ist Vec<Task>, und jede Task besitzt ihren Titel. summarize nimmt &[Task], weil es die Liste nur betrachtet.
Erkläre Ownership in `parse_tasks(input: &str) -> Vec<Task>` und `summarize(tasks: &[Task]) -> String`.
Nutze nur dieses CLI als Beispiel.
Erkläre `String`, `&str`, `Vec<Task>` und `&[Task]` einsteigerfreundlich.
Füge keine unnötigen `clone()`-Aufrufe hinzu.
Ein typischer Fehler ist, Borrow-Checker-Probleme mit überall verteilten clone()-Aufrufen zu überdecken. Manchmal ist ein Clone richtig, aber er braucht eine Begründung.
Kopierbares CLI-Beispiel
Die Logik kommt in src/lib.rs. So kann sie getestet werden, ohne das CLI zu starten.
// src/lib.rs
use anyhow::{Context, Result};
use std::{fs, path::Path};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Task {
pub title: String,
pub done: bool,
}
pub fn parse_tasks(input: &str) -> Vec<Task> {
input.lines().filter_map(parse_task_line).collect()
}
fn parse_task_line(line: &str) -> Option<Task> {
let trimmed = line.trim();
if let Some(title) = trimmed
.strip_prefix("[x] ")
.or_else(|| trimmed.strip_prefix("[X] "))
{
return Some(Task {
title: title.trim().to_string(),
done: true,
});
}
if let Some(title) = trimmed.strip_prefix("[ ] ") {
return Some(Task {
title: title.trim().to_string(),
done: false,
});
}
None
}
pub fn summarize(tasks: &[Task]) -> String {
let total = tasks.len();
let done = tasks.iter().filter(|task| task.done).count();
let open = total.saturating_sub(done);
format!("{total} tasks: {done} done, {open} open")
}
pub fn read_tasks(path: impl AsRef<Path>) -> Result<Vec<Task>> {
let path = path.as_ref();
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path.display()))?;
Ok(parse_tasks(&content))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_markdown_style_tasks() {
let tasks = parse_tasks("[ ] write parser\n[x] add tests\n[X] run clippy\n");
assert_eq!(
tasks,
vec![
Task {
title: "write parser".to_string(),
done: false,
},
Task {
title: "add tests".to_string(),
done: true,
},
Task {
title: "run clippy".to_string(),
done: true,
},
]
);
}
#[test]
fn ignores_unrecognized_lines() {
let tasks = parse_tasks("# Sprint notes\n- plain bullet\n[ ] keep this\n");
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].title, "keep this");
}
#[test]
fn summarizes_counts() {
let tasks = parse_tasks("[ ] one\n[x] two\n[ ] three\n");
assert_eq!(summarize(&tasks), "3 tasks: 1 done, 2 open");
}
}
Der Einstiegspunkt bleibt schlank.
// src/main.rs
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tasknote::{read_tasks, summarize};
#[derive(Parser, Debug)]
#[command(name = "tasknote", about = "Summarize simple task files")]
struct Cli {
#[arg(short, long, default_value = "tasks.txt")]
file: PathBuf,
#[arg(long)]
only_open: bool,
}
fn main() -> Result<()> {
let args = Cli::parse();
let tasks = read_tasks(&args.file)?;
if args.only_open {
for task in tasks.iter().filter(|task| !task.done) {
println!("- {}", task.title);
}
} else {
println!("{}", summarize(&tasks));
}
Ok(())
}
Beispiel für tasks.txt:
[ ] write parser
[x] add unit tests
[ ] run clippy
Prüfe das Projekt so:
cargo fmt
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- --file tasks.txt
cargo run -- --file tasks.txt --only-open
main gibt Result<()> zurück. Wenn die Datei fehlt, kann anyhow::Context den fehlerhaften Pfad anzeigen. Für ein CLI ist das besser als ein panischer unwrap().
Tests, fmt und Clippy in den Auftrag schreiben
Claude Code sollte nicht nur Code liefern, sondern auch die Checks ausführen.
Implementiere `src/lib.rs` und `src/main.rs`.
Führe danach `cargo fmt`, `cargo test` und `cargo clippy --all-targets -- -D warnings` aus.
Wenn etwas fehlschlägt, fasse zuerst den Fehler zusammen und korrigiere mit kleinem Diff.
Nutze `unwrap()` nicht außerhalb von Tests.
Die offizielle Claude-Code-Dokumentation beschreibt es als agentic coding tool, das Codebases lesen, Dateien ändern und Befehle ausführen kann. Trotzdem musst du git diff prüfen und sicherstellen, dass nur die erwarteten Dateien geändert wurden.
Praktische Use Cases
Erster Use Case: Eine Option zu einem bestehenden Rust-CLI hinzufügen. Für --json soll Claude Code summarize stabil halten und eine separate Formatierungsfunktion schreiben.
Zweiter Use Case: Ownership-Fehler verstehen. Gib die vollständige Fehlermeldung weiter und frage, wer den Wert besitzt, wer ihn ausleiht und welche Referenz zu lange lebt.
Dritter Use Case: Bugs test-first beheben. Schreibe Tests für Leerzeilen, [X] und ungültige Zeilen, bevor du den Parser änderst.
Vierter Use Case: Sicher refactoren. Wenn main.rs wächst, trenne Parsing, I/O und Ausgabe, ohne öffentliche Signaturen zu ändern.
Häufige Fallen
Überdecke Borrowing-Probleme nicht mit clone() überall. Frage nach, ob Borrowing reicht.
Lasse unwrap() nicht in produktiven CLI-Pfaden. Dateien, Konfiguration und Nutzereingaben können fehlschlagen.
Refactore nicht ohne Tests. Große KI-Diffs sind schnell erstellt, aber schwer zu prüfen.
Formatiere in einem gemeinsamen Workspace nicht alles, wenn nur ein Crate betroffen ist.
Sicherer Refactoring-Prompt
Refactore den Parser von `tasknote` sicher.
Bedingungen:
- Bedeutung von `[ ] task` und `[x] task` beibehalten
- Öffentliche Signaturen von `parse_tasks`, `summarize`, `read_tasks` nicht ändern
- Nur `src/lib.rs` und seine Tests anfassen
- Keine unnötigen `clone()`-Aufrufe hinzufügen
- Zuerst einen 3-Zeilen-Plan ausgeben und auf Freigabe warten
Nach der Änderung:
- `cargo fmt`
- `cargo test`
- `cargo clippy --all-targets -- -D warnings`
- Diff und Ownership-Entscheidungen zusammenfassen
So bekommt der Agent Grenzen, Checks und Berichtspflichten. Rust passt dazu, weil Compiler, Tests und Clippy schnelle Rückmeldung geben.
CTA und nächste Schritte
Zum Üben kannst du JSON-Ausgabe, CSV-Export, Verzeichnis-Scan oder ein serde-Format ergänzen. Im Team gehören Rust edition, Pflichtbefehle, unwrap()-Regeln, erlaubte Crates und Review-Checkliste in CLAUDE.md.
ClaudeCodeLab bietet Prompts, Vorlagen, Setup-Guides und Teamtraining für Claude Code. Starte mit dem kostenlosen Cheatsheet, prüfe Produkte und Templates oder nutze Training und Beratung für dein Repository. Passende Folgeartikel sind TDD mit Claude Code und die Review-Workflow-Checkliste.
Testnotiz: Am stabilsten war der Ablauf, die Logik zuerst in src/lib.rs zu halten, Verhalten mit cargo test festzuschreiben und Claude Code Ownership erklären zu lassen, bevor es editiert. Dadurch entstehen kleinere Diffs und weniger unnötige clone()-Aufrufe.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.