Retour au cours

backend / rust

Modules et organisation de crate

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Structurer un projet Rust réel avec des modules organisés en dossiers
  • Contrôler la visibilité d'un élément avec pub
  • Utiliser crate::, super:: et self:: pour référencer un module précisément
  • Ré-exporter un sous-module pour simplifier les chemins d'import ailleurs
  • Ajouter et explorer des dépendances externes via cargo add et cargo tree

Dans quel contexte ?

Une équipe fait grandir un projet Rust qui tenait au départ dans un seul main.rs de quelques centaines de lignes, jusqu'à devenir un service métier complexe. Sans organisation en modules, ce fichier devient vite ingérable, et surtout, aucun mécanisme ne protège les détails d'implémentation internes contre une utilisation accidentelle depuis un autre endroit du code qui ne devrait pas y avoir accès.

D'abord, un module en Rust correspond généralement à un dossier ou un fichier

mod modeles; dans main.rs déclare un module local, que Rust va chercher soit dans modeles.rs, soit dans modeles/mod.rs. Cette seconde forme permet à un module d'être lui-même composé de plusieurs sous-fichiers, organisés dans un dossier dédié — la structure la plus courante pour un projet de taille réelle.

Une fois cette organisation posée, la visibilité se contrôle avec un seul mot-clé

Par défaut, tout est privé au module qui le déclare. pub rend un élément (fonction, struct, champ) visible depuis l'extérieur de son module. Une struct peut avoir certains champs publics et d'autres privés simultanément, un contrôle fin utile pour exposer un constructeur tout en cachant les détails internes.

Préfixe de cheminSignifieUsage typique
crate::Chemin absolu depuis la racine du crateRéférencer un module quelle que soit sa profondeur
super::Remonte d'un niveau dans l'arborescenceAccéder au module parent
self::Reste dans le module courantRarement nécessaire, souvent implicite

Prérequis

Il faut avoir écrit au moins un petit projet avec cargo new (première leçon du cours) pour situer où les fichiers de modules s'insèrent concrètement dans l'arborescence générée.

Il reste un raccourci très pratique pour simplifier les imports ailleurs

pub use utilisateur::Utilisateur; dans un fichier mod.rs ré-exporte un type depuis un sous-module, permettant à l'extérieur d'écrire use modeles::Utilisateur plutôt que le chemin complet use modeles::utilisateur::Utilisateur. C'est un détail d'ergonomie qui améliore beaucoup la lisibilité des imports dans un gros projet.

Piège fréquent

Oublier pub sur un champ de struct qu'on veut manipuler depuis un autre module produit une erreur de compilation ("field is private"), même si la struct elle-même est publique. La visibilité en Rust se contrôle à chaque niveau — module, type, champ, méthode — indépendamment les uns des autres.

Enfin, comment ajouter une bibliothèque tierce à un projet

Bonne pratique

Utilise cargo add <nom-de-crate> plutôt que d'éditer manuellement Cargo.toml : cette commande résout automatiquement la dernière version compatible et met à jour le fichier correctement. cargo tree affiche ensuite l'arbre complet des dépendances, utile pour repérer une dépendance transitive inattendue ou en conflit de version.

Maintenant que tu sais organiser un projet Rust à grande échelle, la prochaine leçon aborde comment vérifier automatiquement que chaque module se comporte comme prévu : les tests.

Commandes & code

Modules et organisation de crate

Structurer un projet Rust réel avec des modules, une visibilité claire et des chemins explicites.

bash
mon-projet/
  Cargo.toml
  src/
    main.rs           <- point d'entree du binaire
    lib.rs             <- point d'entree de la bibliotheque (si crate hybride)
    config.rs
    modeles/
      mod.rs
      utilisateur.rs
      produit.rs
    services/
      mod.rs
      utilisateur_service.rs
rust
// fichier: src/modeles/utilisateur.rs
pub struct Utilisateur {
    pub nom: String,
    email: String, // prive par defaut : accessible uniquement dans ce module
}

impl Utilisateur {
    pub fn nouveau(nom: &str, email: &str) -> Self {
        Self { nom: nom.to_string(), email: email.to_string() }
    }

    pub fn email(&self) -> &str { // accesseur public pour un champ prive
        &self.email
    }
}
rust
// fichier: src/modeles/mod.rs
pub mod utilisateur; // declare le sous-module et le rend accessible
pub mod produit;

pub use utilisateur::Utilisateur; // re-export : simplifie les chemins d'import ailleurs
rust
// fichier: src/main.rs
mod modeles;   // declare le module local (cherche modeles/mod.rs ou modeles.rs)
mod services;
mod config;

use modeles::Utilisateur; // grace au re-export dans mod.rs
use services::utilisateur_service; // chemin complet vers le sous-module

fn main() {
    let u = Utilisateur::nouveau("Alice", "alice@example.com");
    println!("{} <{}>", u.nom, u.email());

    utilisateur_service::afficher_bienvenue(&u);
}
rust
// fichier: src/services/utilisateur_service.rs
use crate::modeles::Utilisateur; // "crate::" = chemin absolu depuis la racine du crate

pub fn afficher_bienvenue(u: &Utilisateur) {
    println!("Bienvenue, {} !", u.nom);
}

// super:: pour remonter d'un niveau dans l'arborescence de modules
// self:: pour rester dans le module courant
bash
# Dependances externes : ajoutees dans Cargo.toml, gerees par crates.io
cargo add serde --features derive
cargo add reqwest --features json
cargo tree   # visualiser l'arbre complet des dependances

Résumé

  • pub rend un item public ; sans pub, il n'est visible que dans son module et ses enfants.
  • mod.rs (ou nom_module.rs) regroupe et ré-exporte les sous-modules d'un dossier.
  • crate::, super::, self:: sont les préfixes de chemins absolus/relatifs entre modules.
  • cargo add + Cargo.toml gèrent les dépendances externes hébergées sur crates.io.

Exercices pratiques

1 disponible
1

Mission : reorganiser un main.rs de 2000 lignes

Objectif : Diagnostiquer des erreurs de visibilite et de chemins de modules dans un projet en cours de decoupage, et les corriger.

Contexte

Une equipe decoupe enfin son gigantesque main.rs en modules. Elle a cree src/modeles/commande.rs avec une struct Commande, et src/services/facturation.rs qui doit l'utiliser. Apres le decoupage, le projet ne compile plus avec deux erreurs distinctes : 'field montant is private' dans facturation.rs, et 'unresolved import crate::modeles::Commande' dans main.rs, alors que pub mod commande; est bien present dans modeles/mod.rs.

Résoudre l’exercice →