Aller au contenu

Cargo et les crates en Rust

Sur la page Débuter avec Rust, vous avez utilisé cargo new et cargo run. Ce n’était qu’un début. Cargo est l’outil de compilation de Rust et son gestionnaire de paquets. Il compile votre code, lance vos tests, formate vos fichiers et télécharge les bibliothèques écrites par d’autres.

Ces bibliothèques s’appellent des crates. Voyez-les comme les applications d’un magasin d’applications : quelqu’un a déjà résolu un problème (lire du JSON, faire des requêtes web, analyser des dates), et vous pouvez ajouter sa solution à votre projet en une seule commande. Cette page vous montre comment ça marche, et comment choisir une bonne crate.

Deux mots reviennent sans cesse :

  • Une crate est une unité de code Rust que le compilateur construit. C’est soit un binaire (un programme que vous pouvez lancer, qui commence dans src/main.rs), soit une bibliothèque (du code destiné à d’autres programmes, qui commence dans src/lib.rs).
  • Un paquet (package) est ce que crée cargo new : un dossier avec un fichier Cargo.toml et une ou plusieurs crates à l’intérieur.

Dans la vie de tous les jours, on dit « crate » pour les deux. Quand quelqu’un dit « utilise la crate serde », il veut dire « ajoute le paquet serde de crates.io à ton projet ».

Chaque paquet a un fichier Cargo.toml. Il est écrit en TOML, un format de configuration simple. Voici ce que vous donne cargo new hello_rust :

[package]
name = "hello_rust"
version = "0.1.0"
edition = "2024"
[dependencies]
  • [package] décrit votre projet. name et version sont ce que les autres verraient si vous le publiiez.
  • edition choisit l’édition du langage Rust à utiliser. Les éditions (2018, 2021, 2024) permettent à Rust de s’améliorer sans casser le code existant. Gardez simplement ce que cargo new a écrit.
  • [dependencies] liste les crates que votre code utilise. Au départ, la section est vide.

Quand un projet grandit, d’autres sections peuvent apparaître :

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
# utilisées seulement par les tests et les exemples, pas par le vrai programme
pretty_assertions = "1.4"
[profile.release]
# réglages pour `cargo build --release`
opt-level = 3

Vous pouvez modifier [dependencies] à la main, mais le plus simple est cargo add. Cette commande trouve la dernière version sur crates.io et écrit la ligne pour vous :

Terminal window
cargo add serde_json

Certaines crates ont des parties optionnelles appelées features (fonctionnalités). Elles sont désactivées par défaut pour garder votre build léger. Vous les activez avec --features :

Terminal window
cargo add serde --features derive

Après ces deux commandes, votre Cargo.toml contiendra quelque chose comme ceci :

[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"

Utilisez maintenant la crate dans votre code. Le prochain cargo run la télécharge, la compile une fois, et la lie à votre programme :

use serde_json::json;
fn main() {
let crab = json!({ "legs": 10, "name": "Ferris" });
println!("{}", crab);
println!("Nom : {}", crab["name"]);
}

Sortie :

{"legs":10,"name":"Ferris"}
Nom : "Ferris"
▶ Essayez par vous-même

Les guillemets autour de "Ferris" sont là parce que crab["name"] est toujours une valeur JSON, pas une simple chaîne Rust.

Le Rust Playground inclut déjà les crates les plus populaires, donc ce code y fonctionne sans cargo add. Sur votre machine, il faut d’abord passer par cargo add serde_json. Vous en apprendrez beaucoup plus sur cette crate à la page JSON avec Serde.

Pour retirer une crate dont vous n’avez plus besoin, lancez cargo remove serde_json.

Quand vous écrivez serde_json = "1.0", vous ne demandez pas exactement la version 1.0. Vous dites « n’importe quelle version compatible avec 1.0 ». Cargo suit le versionnage sémantique (SemVer) : une version s’écrit MAJEUR.MINEUR.CORRECTIF, et seul un changement du numéro majeur a le droit de casser votre code.

Vous écrivez Cargo peut utiliser Pourquoi
"1.0" (identique à "^1.0") >=1.0.0 et <2.0.0 toutes les versions 1.x doivent être compatibles
"1.4.2" >=1.4.2 et <2.0.0 au moins 1.4.2, mais toujours 1.x
"0.4" >=0.4.0 et <0.5.0 avant 1.0, c’est le numéro mineur qui compte comme cassant
"~1.4" >=1.4.0 et <1.5.0 seulement les correctifs
"=1.4.2" exactement 1.4.2 rarement nécessaire

Alors, qui décide de la version exacte ? C’est le rôle de Cargo.lock. À la première compilation, Cargo choisit les versions les plus récentes qui respectent vos exigences et les note dans Cargo.lock. Toutes les compilations suivantes utilisent exactement les mêmes versions, sur votre machine comme sur celles des autres.

  • Ne modifiez pas Cargo.lock à la main. C’est Cargo qui le gère.
  • Ajoutez-le à git, surtout pour les programmes. Ainsi, vos coéquipiers et votre CI compilent exactement ce que vous avez compilé.
  • cargo update passe aux versions les plus récentes qui respectent encore Cargo.toml, et réécrit Cargo.lock. Il ne saute jamais à une nouvelle version majeure : pour ça, c’est vous qui modifiez Cargo.toml.

Trois sites vous serviront encore et encore :

  • crates.io est le registre officiel. Toutes les crates publiques s’y trouvent. Cherchez un mot comme « csv » ou « date ».
  • docs.rs héberge la documentation de chaque crate, générée automatiquement. Par exemple, docs.rs/serde_json.
  • lib.rs est un index non officiel, plus rapide, qui classe les crates par catégorie. Idéal pour explorer.

Beaucoup de crates résolvent le même problème : comment choisir ? Avant d’en ajouter une, prenez une minute pour vérifier :

  1. Les téléchargements. Les crates populaires ont des millions de téléchargements. Cela veut dire que beaucoup de gens ont trouvé et corrigé les bugs avant vous.
  2. Les mises à jour récentes. Regardez la date de la dernière version. Une crate qui n’a pas bougé depuis des années peut très bien convenir (certaines sont simplement « terminées »), mais vérifiez que son dépôt n’est pas plein de tickets sans réponse.
  3. La documentation. Ouvrez sa page sur docs.rs. Les bonnes crates ont une page d’accueil claire avec un exemple à copier.
  4. Une version 1.0 ou plus. Ce n’est pas obligatoire, mais cela indique que l’auteur promet une API stable.
  5. Peu de dépendances. Chaque crate que vous ajoutez apporte ses propres dépendances. Plus c’est petit, plus il est facile de lui faire confiance.

En cas de doute, cherchez « rust » suivi de votre problème, et regardez quelle crate la communauté recommande le plus souvent.

Inutile de les apprendre par cœur. Gardez ce tableau sous la main, et vous les retiendrez à force de les utiliser :

Commande Ce qu’elle fait
cargo new my_app Crée un nouveau projet binaire (ajoutez --lib pour une bibliothèque)
cargo build Compile dans target/debug/
cargo run Compile et lance (les arguments se passent après -- : cargo run -- input.txt)
cargo check Cherche les erreurs sans produire de programme. Bien plus rapide que build
cargo test Lance vos tests (voir Les tests)
cargo fmt Formate tout votre code dans le style standard
cargo clippy Lance le linter. Il suggère du code plus simple et plus idiomatique
cargo doc --open Génère la documentation HTML de votre projet et de chaque dépendance, puis l’ouvre
cargo update Met à jour Cargo.lock vers les versions compatibles les plus récentes
cargo build --release Compile un programme optimisé dans target/release/

Une bonne habitude : lancez souvent cargo check pendant que vous écrivez, et cargo fmt et cargo clippy avant de faire un commit. cargo doc --open est aussi une petite pépite méconnue : il vous donne la documentation des versions exactes des crates que vous utilisez, et il fonctionne hors ligne.

Par défaut, Cargo produit un build debug. Il compile vite et garde des vérifications de sécurité supplémentaires, comme une panic quand un entier déborde. C’est parfait pendant le développement.

Un build release (cargo build --release ou cargo run --release) prend plus de temps à compiler, mais s’exécute bien plus vite, souvent 10 fois plus ou davantage. Utilisez-le quand vous distribuez votre programme ou mesurez ses performances.

Votre code peut même savoir dans quel type de build il se trouve :

fn main() {
if cfg!(debug_assertions) {
println!("Build debug : vérifications supplémentaires activées");
} else {
println!("Build release : optimisé pour la vitesse");
}
}

Sortie :

Build debug : vérifications supplémentaires activées
▶ Essayez par vous-même

Dans le Playground, vous pouvez passer de « Debug » à « Release » dans le menu, puis relancer pour voir l’autre ligne.

Un paquet peut contenir une bibliothèque et un binaire en même temps. C’est une organisation très courante : vous gardez la vraie logique dans src/lib.rs, et src/main.rs reste petit et se contente de l’appeler :

greeter/
├── Cargo.toml
└── src/
├── lib.rs # la crate bibliothèque : la logique réutilisable
└── main.rs # la crate binaire : le programme que vous lancez
src/lib.rs
pub fn greet(name: &str) -> String {
format!("Bonjour, {name} !")
}
src/main.rs
fn main() {
// la bibliothèque porte le nom du paquet
println!("{}", greeter::greet("Ferris"));
}

Pourquoi découper ainsi ? Le code de lib.rs est facile à tester, et d’autres programmes (ou un second binaire) peuvent le réutiliser. Le Playground n’a qu’un seul fichier, mais vous pouvez retrouver la même idée avec un pub mod, comme sur la page Les modules :

mod greeter {
pub fn greet(name: &str) -> String {
format!("Bonjour, {name} !")
}
}
fn main() {
println!("{}", greeter::greet("Ferris"));
}

Sortie :

Bonjour, Ferris !
▶ Essayez par vous-même

Quand un projet grandit et se compose de plusieurs paquets qui vont ensemble (par exemple une application en ligne de commande, un serveur web et une bibliothèque commune), vous pouvez les regrouper dans un workspace (espace de travail). Ils partagent un seul Cargo.lock et un seul dossier target/ : tout se compile ensemble et utilise les mêmes versions de dépendances.

Le Cargo.toml à la racine liste les membres :

[workspace]
members = ["app", "core"]
resolver = "3"
my_project/
├── Cargo.toml # le fichier de workspace ci-dessus
├── Cargo.lock # partagé par tous les membres
├── app/ # un paquet binaire
└── core/ # un paquet bibliothèque

Ensuite, cargo build à la racine compile tout, et cargo run -p app lance un seul membre. Vous n’en aurez pas besoin pour vos premiers projets, mais c’est utile de savoir le reconnaître en lisant le code des autres.

Utiliser une crate sans l’avoir ajoutée. Si vous écrivez use serde_json::json; mais avez oublié cargo add serde_json, le compilateur ne la trouve pas :

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`

Le compilateur vous dit que l’import serde_json est introuvable et qu’il vous manque sans doute une crate de ce nom. La solution : lancer cargo add serde_json. Vérifiez aussi l’orthographe : les noms de crates utilisent - ou _, et dans le code, un - devient toujours _.

Oublier une feature. Si #[derive(Serialize)] indique qu’il ne trouve pas la macro derive, vous avez probablement ajouté serde sans --features derive. Relancez cargo add serde --features derive : la commande met à jour la ligne existante.

Mesurer la vitesse avec un build debug. Si votre programme vous paraît lent, essayez cargo run --release avant de toucher au code.

Utilisez la macro json! de serde_json pour décrire une crate avec trois clés : downloads (un nombre), name et version. Affichez la valeur entière, puis seulement la version.

Indice : reprenez la forme de l’exemple crab dans Ajouter une dépendance avec cargo add. Sur votre machine, lancez d’abord cargo add serde_json ; le Playground l’a déjà.

Voir la solution
use serde_json::json;
fn main() {
let krate = json!({
"downloads": 500,
"name": "serde_json",
"version": "1.0"
});
println!("{}", krate);
println!("Version : {}", krate["version"]);
}

Sortie :

{"downloads":500,"name":"serde_json","version":"1.0"}
Version : "1.0"
▶ Essayez par vous-même

La variable s’appelle krate parce que crate est un mot réservé en Rust. Cette orthographe est une blague classique dans le code Rust. Comme avant, la version s’affiche avec des guillemets car c’est toujours une valeur JSON ; la page JSON avec Serde montre comment en extraire du texte brut.

Écrivez une fonction debug_log(msg: &str) qui affiche [debug] suivi du message, mais seulement dans un build debug. Utilisez-la dans main autour d’un petit calcul, et affichez le résultat normalement.

Indice : entourez le println! d’un if cfg!(debug_assertions) { ... }, comme dans Compilation debug ou release.

Voir la solution
fn debug_log(msg: &str) {
if cfg!(debug_assertions) {
println!("[debug] {msg}");
}
}
fn main() {
debug_log("début du calcul");
let mut total = 0;
for n in 1..=10 {
total += n;
}
debug_log("calcul terminé");
println!("La somme de 1 à 10 vaut {total}");
}

Sortie :

[debug] début du calcul
[debug] calcul terminé
La somme de 1 à 10 vaut 55
▶ Essayez par vous-même

Passez le Playground en « Release » (ou utilisez cargo run --release en local) et relancez : seule la dernière ligne s’affiche. Pour de vrais projets, la page Journalisation présente des crates qui font ça proprement, avec des niveaux comme debug et info.

  • Cargo compile, lance, teste et formate votre code, et télécharge des crates depuis crates.io.
  • Cargo.toml contient ce que vous demandez ; Cargo.lock enregistre les versions exactes obtenues. Ajoutez les deux à git.
  • cargo add nom --features x ajoute une dépendance. "1.0" veut dire « n’importe quelle version 1.x compatible ».
  • Jugez une crate à ses téléchargements, ses mises à jour récentes et sa bonne documentation sur docs.rs.
  • Utilisez cargo check pendant l’écriture, cargo fmt et cargo clippy avant un commit, et --release pour la vitesse.
  • Mettez la logique réutilisable dans src/lib.rs et gardez src/main.rs petit.

Petit quiz

1. Dans Cargo.toml, que signifie serde_json = "1.0" ?

2. Quel fichier enregistre les versions exactes de chaque dépendance utilisée par votre build ?

3. Vous voulez chercher les erreurs dans votre code le plus vite possible, sans produire de programme. Quelle commande convient le mieux ?

4. Comment ajouter serde avec sa feature derive activée ?

Score : 0 / 4