Développement Rust avec Claude Code : Cargo, ownership, tests et refactorisation CLI
Guide Rust avec Claude Code : Cargo, ownership, tests, fmt, clippy, gestion d'erreurs et petit CLI copiable.
Commencer par une tâche Rust petite et vérifiable
Rust demande de comprendre ownership, borrowing, lifetimes et gestion d’erreurs avec Result. Pour un débutant, on peut traduire simplement : ownership signifie “qui possède la donnée”, borrowing signifie “qui l’emprunte temporairement”, et lifetime signifie “combien de temps cette référence reste valide”. Claude Code est utile si on l’utilise comme assistant de développement rigoureux : il lit les erreurs du compilateur, explique les choix, modifie des fichiers précis et lance les commandes de vérification.
Dans ce guide, nous construisons un petit CLI nommé tasknote. Il lit un fichier tasks.txt avec des lignes [ ] task et [x] task, puis affiche un résumé ou seulement les tâches ouvertes. Les sources à garder sous la main sont la documentation officielle : Rust Book sur ownership, Cargo Book pour créer un projet, cargo test, rustfmt, Clippy et Claude Code overview.
Si vous débutez avec l’outil, commencez par le guide de démarrage Claude Code. Pour une équipe, documentez les commandes et règles dans CLAUDE.md best practices et limitez les actions avec le guide des permissions.
flowchart LR
Prompt["Objectif et limites"]
Cargo["Petit projet Cargo"]
Compiler["Lire les erreurs d'ownership"]
Tests["Figer le comportement"]
Quality["fmt et clippy"]
Refactor["Refactorisation ciblée"]
Prompt --> Cargo --> Compiler --> Tests --> Quality --> Refactor
Créer le projet avec Cargo
Cargo est l’outil standard pour créer, compiler, exécuter, tester et gérer les dépendances Rust. Avant de demander du code à Claude Code, définissez les commandes de vérification. Cela évite une réponse jolie mais non vérifiée.
cargo new tasknote --bin
cd tasknote
cargo run
Le projet contient Cargo.toml et src/main.rs. Les projets récents peuvent utiliser edition = "2024", donc demandez à Claude Code de lire le manifeste réel au lieu de copier une configuration ancienne. Ici, nous gardons deux dépendances : clap pour les arguments CLI et anyhow pour ajouter du contexte aux erreurs.
[package]
name = "tasknote"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
Le premier prompt doit demander une conception, pas une implémentation complète.
Dans ce projet Cargo, je veux créer un CLI `tasknote`.
Il lit des lignes `[ ] task` et `[x] task` depuis `tasks.txt`.
Retourne d'abord seulement le design de `Cargo.toml`, `src/lib.rs` et `src/main.rs`.
N'édite pas encore les fichiers. Explique les choix d'ownership et de gestion d'erreurs.
Cette pause est importante. En Rust, savoir si une fonction possède une valeur, l’emprunte ou en retourne une nouvelle change la simplicité du code.
Poser les bonnes questions sur ownership
Dans ce CLI, le contenu du fichier devient un String. parse_tasks lit ce texte comme &str, puis produit un Vec<Task> qui possède les titres. summarize ne fait que lire la liste, donc il prend &[Task].
Explique l'ownership de `parse_tasks(input: &str) -> Vec<Task>` et `summarize(tasks: &[Task]) -> String`.
Utilise uniquement ce CLI comme exemple.
Explique `String`, `&str`, `Vec<Task>` et `&[Task]` pour un débutant.
N'ajoute pas de `clone()` inutile.
L’erreur fréquente consiste à demander “répare le borrow checker” et accepter une solution remplie de clone(). Cloner peut être correct, mais cela doit être un choix clair. Si une fonction lit seulement, l’emprunt est souvent plus expressif.
Exemple CLI copiable
Placez la logique dans src/lib.rs. Le code devient testable sans lancer le CLI, et main.rs reste court.
// 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");
}
}
Puis src/main.rs :
// 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(())
}
Fichier tasks.txt :
[ ] write parser
[x] add unit tests
[ ] run clippy
Commandes à lancer :
cargo fmt
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- --file tasks.txt
cargo run -- --file tasks.txt --only-open
main retourne Result<()>, donc une erreur de fichier peut être affichée avec un contexte utile. Pour un CLI, un fichier manquant est une erreur récupérable : il faut aider l’utilisateur à corriger le chemin.
Ajouter tests, fmt et Clippy à la demande
Claude Code doit produire du code et des preuves de vérification. Ajoutez les commandes dans la demande.
Implémente `src/lib.rs` et `src/main.rs`.
Après modification, exécute `cargo fmt`, `cargo test` et `cargo clippy --all-targets -- -D warnings`.
Si une commande échoue, résume l'erreur puis corrige avec le plus petit diff raisonnable.
N'utilise pas `unwrap()` hors des tests.
La documentation de Claude Code le décrit comme un outil agentique capable de lire un codebase, modifier des fichiers et exécuter des commandes. Cela ne remplace pas la revue humaine : relisez le git diff, vérifiez les fichiers touchés et relancez les commandes critiques.
Cas d’usage concrets
Premier cas : ajouter une option à un CLI existant. Pour --json, demandez de garder summarize stable et d’ajouter une fonction de formatage séparée.
Deuxième cas : apprendre à partir des erreurs d’ownership. Collez le message complet et demandez qui possède la valeur, qui l’emprunte et quelle référence vit trop longtemps.
Troisième cas : corriger un bug avec les tests d’abord. Ajoutez des tests pour lignes vides, [X] et lignes invalides avant de modifier le parser.
Quatrième cas : refactoriser sans casser. Quand main.rs grossit, séparez parsing, I/O et affichage sans changer les signatures publiques.
Pièges à éviter
Ne résolvez pas tous les problèmes de borrowing avec clone(). Demandez si la fonction a vraiment besoin de posséder la donnée.
Ne laissez pas unwrap() dans les chemins de production. Les fichiers et entrées utilisateur échouent souvent.
Ne refactorisez pas sans tests. Claude Code peut produire de grands diffs rapidement, mais ils sont difficiles à relire.
N’exécutez pas un formatage global dans un workspace partagé sans préciser le périmètre.
Prompt sûr pour refactoriser
Refactorise prudemment le parser de `tasknote`.
Contraintes:
- Préserver le sens de `[ ] task` et `[x] task`
- Ne pas changer les signatures publiques de `parse_tasks`, `summarize` et `read_tasks`
- Toucher seulement `src/lib.rs` et ses tests
- Ne pas ajouter de `clone()` inutile
- Proposer d'abord un plan de 3 lignes et attendre validation
Après édition:
- Exécuter `cargo fmt`
- Exécuter `cargo test`
- Exécuter `cargo clippy --all-targets -- -D warnings`
- Résumer le diff et les décisions d'ownership
Ce prompt donne à l’agent un cadre : limites, checks et rapport. Rust fonctionne très bien avec cette méthode car le compilateur, les tests et Clippy réagissent vite.
CTA et suite
Pour pratiquer, ajoutez une sortie JSON, un export CSV, un scan de dossiers ou un format avec serde. Pour une équipe, écrivez dans CLAUDE.md l’édition Rust, les commandes obligatoires, la politique sur unwrap(), les crates autorisées et la checklist de revue.
ClaudeCodeLab propose des prompts, modèles et formations pour standardiser Claude Code. Commencez par la fiche gratuite, consultez les produits et modèles ou demandez une formation et consultation sur votre dépôt. À lire aussi : TDD avec Claude Code et checklist de revue.
Note de test : ce flux est le plus fiable quand la logique reste dans src/lib.rs, que cargo test fixe le comportement et que Claude Code explique l’ownership avant de modifier. Les diffs sont alors plus petits et contiennent moins de clone() inutiles.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.