Modules

Modules

Structurer vos Projets Rust

Modules, Crates et Workspaces

1. Les Modules (mod)

Les modules permettent d'organiser le code en namespaces. C'est essentiel pour structurer de gros projets !

1.1 - Module inline

// Module défini directement dans le fichier
mod network {
    fn connect() {
        println!("Connexion...");
    }
    pub fn send_data() {
        connect();  // Peut appeler connect() (même module)
        println!("Envoi de données");
    }
}

fn main() {
    // network::connect();  // ERREUR : connect est privé
    network::send_data();   // OK : send_data est pub
}
// Modules imbriqués
mod reseau {
    pub mod tcp {
        pub fn connect() {}
    }
    pub mod udp {
        pub fn send() {}
    }
}

fn main() {
    reseau::tcp::connect();
    reseau::udp::send();
}

1.2 - Module dans un fichier

// Structure de fichiers :
// src/
//   main.rs
//   network.rs
// Dans main.rs :
mod network;  // Cherche network.rs
fn main() {
    network::connect();
}
// Dans network.rs :
pub fn connect() {
    println!("Connexion établie");
}
// Avec sous-modules :
// src/
//   main.rs
//   network.rs
//   network/
//     tcp.rs
//     udp.rs
// Dans network.rs :
pub mod tcp;  // Cherche network/tcp.rs
pub mod udp;  // Cherche network/udp.rs
pub fn common_function() {}

Convention : utiliser mod.rs pour les modules avec sous-modules, ou un fichier portant le nom du module.

1.3 - Hiérarchie de modules

// Structure :
// src/
//   lib.rs
//   auth/
//     mod.rs
//     login.rs
//     register.rs
//   database/
//     mod.rs
//     connection.rs
//     query.rs
// Dans lib.rs :
pub mod auth;
pub mod database;
// Dans auth/mod.rs :
pub mod login;
pub mod register;
// Utilisation
use mon_projet::auth::login::authenticate;
use mon_projet::database::connection::connect;

2.1 - Privé par défaut

mod api {
    // Fonction privée (par défaut)
    fn interne() {
        println!("Fonction interne");
    }
    // Fonction publique
    pub fn publique() {
        interne();  // OK dans le même module
        println!("Fonction publique");
    }
    // Struct privée
    struct Config {
        secret: String,
    }
    // Struct publique avec champ privé
    pub struct User {
        pub nom: String,
        mot_de_passe: String,  // Privé !
    }
    impl User {
        pub fn new(nom: String, mdp: String) -> User {
            User { nom, mot_de_passe: mdp }
        }
    }
}

fn main() {
    let user = api::User::new(
        String::from("Alice"),
        String::from("secret123"),
    );
    println!("{}", user.nom);  // OK
    // println!("{}", user.mot_de_passe);  // ERREUR : privé
}

2.2 - pub et pub(crate)

// pub : visible partout
pub fn global() {}
// pub(crate) : visible dans la crate uniquement
pub(crate) fn interne_crate() {}
// pub(super) : visible dans le module parent
mod parent {
    pub(super) fn pour_parent() {}
    mod enfant {
        pub(in crate::parent) fn specifique() {}
    }
}
// Exemple pratique
mod database {
    pub struct Connection {
        url: String,
    }
    impl Connection {
        pub fn new(url: String) -> Self {
            Self { url }
        }
        // Méthode interne à la crate
        pub(crate) fn raw_query(&self, sql: &str) {
            // Fonction dangereuse, pas exposée publiquement
        }
    }
}

2.3 - Re-exports

// Dans lib.rs
mod internal {
    pub struct User {
        pub nom: String,
    }
}
// Re-export pour simplifier l'API
pub use internal::User;
// Les utilisateurs peuvent faire :
use ma_lib::User;  // Au lieu de ma_lib::internal::User
// Re-exports multiples
mod database {
    pub mod mysql {
        pub fn connect() {}
    }
    pub mod postgres {
        pub fn connect() {}
    }
}
// Simplifier l'API
pub use database::mysql;
pub use database::postgres;

3.1 - Importer des items

// Import simple
use std::collections::HashMap;
let mut map = HashMap::new();
// Import multiple
use std::collections::{HashMap, HashSet, BTreeMap};
// Import tout le module
use std::io;
io::stdin().read_line(&mut buffer)?;
// Import avec renommage
use std::collections::HashMap as Map;
let mut map = Map::new();
// Import imbriqué
use std::{
    io::{self, Write},
    collections::HashMap,
};

3.2 - Chemins absolus vs relatifs

// Chemin absolu (depuis la racine de la crate)
use crate::network::tcp::connect;
// Chemin relatif
mod network {
    pub mod tcp {
        pub fn connect() {}
    }
    pub mod udp {
        // Utiliser super pour remonter
        use super::tcp;
        pub fn send() {
            tcp::connect();
        }
    }
}
// self fait référence au module actuel
mod parent {
    pub fn fonction() {}
    mod enfant {
        use self::super::fonction;  // Remonte d'un niveau
    }
}

3.3 - use as et glob

// Renommer pour éviter les conflits
use std::io::Result as IoResult;
use std::fmt::Result as FmtResult;
fn read() -> IoResult<String> { /* ... */ }
fn format() -> FmtResult { /* ... */ }
// Glob import (à éviter généralement)
use std::collections::*;
// Acceptable pour le prelude
use std::prelude::v1::*;
// Ou pour tests
#[cfg(test)]
mod tests {
    use super::*;  // Import tout du module parent
    #[test]
    fn test_fonction() {
        assert!(ma_fonction());
    }
}

Éviter use * : ça pollue le namespace et rend le code moins clair. À réserver aux tests ou au prelude.

4.1 - lib.rs vs main.rs

// Structure projet :
// mon_projet/
//   Cargo.toml
//   src/
//     lib.rs      ← Bibliothèque (optionnel)
//     main.rs     ← Binaire
//     bin/        ← Binaires additionnels (optionnel)
//       autre.rs
// Dans lib.rs :
pub fn fonction_publique() -> i32 {
    42
}
fn fonction_privee() {
    // Utilisable seulement dans la lib
}
// Dans main.rs :
use mon_projet::fonction_publique;
fn main() {
    let x = fonction_publique();
    println!("{}", x);
}
// Cargo.toml :
// [package]
// name = "mon_projet"
// version = "0.1.0"
//
// [lib]
// name = "mon_projet"
// path = "src/lib.rs"
//
// [[bin]]
// name = "mon_projet"
// path = "src/main.rs"

4.2 - Créer une lib

# Créer une nouvelle bibliothèque
cargo new ma_lib --lib
# Structure générée :
# ma_lib/
#   Cargo.toml
#   src/
#     lib.rs
# Dans lib.rs :
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }
}
# Utiliser la lib dans un autre projet :
# Cargo.toml :
# [dependencies]
# ma_lib = { path = "../ma_lib" }
# Dans le code :
use ma_lib::add;
fn main() {
    println!("{}", add(5, 3));
}

5.1 - Dépendances

# Cargo.toml
[package]
name = "mon_projet"
version = "0.1.0"
edition = "2021"
[dependencies]
# Depuis crates.io
serde = "1.0"
# Version spécifique
tokio = "=1.35.0"
# Features optionnelles
serde = { version = "1.0", features = ["derive"] }
# Depuis git
mon_lib = { git = "https://github.com/user/repo" }
# Depuis un chemin local
ma_lib = { path = "../ma_lib" }
[dev-dependencies]
# Seulement pour les tests
criterion = "0.5"
[build-dependencies]
# Pour build.rs
cc = "1.0"
# Différentes dépendances selon la plateforme
[target.'cfg(windows)'.dependencies]
winapi = "0.3"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

5.2 - Features

# Dans Cargo.toml
[features]
default = ["json"]
json = ["serde_json"]
xml = ["quick-xml"]
full = ["json", "xml"]
[dependencies]
serde_json = { version = "1.0", optional = true }
quick-xml = { version = "0.31", optional = true }
# Dans le code (lib.rs) :
#[cfg(feature = "json")]
pub mod json_parser {
    pub fn parse() {}
}
#[cfg(feature = "xml")]
pub mod xml_parser {
    pub fn parse() {}
}
# Utilisation :
# cargo build --features json
# cargo build --features "json xml"
# cargo build --all-features

6. Workspaces

Les workspaces permettent de gérer plusieurs crates dans un même dépôt.

# Structure :
# mon_workspace/
#   Cargo.toml       ← Workspace root
#   ma_lib/
#     Cargo.toml
#     src/lib.rs
#   mon_app/
#     Cargo.toml
#     src/main.rs
#   mon_autre_lib/
#     Cargo.toml
#     src/lib.rs
# Dans mon_workspace/Cargo.toml :
[workspace]
members = ["ma_lib", "mon_app", "mon_autre_lib"]

# Dépendances partagées
[workspace.dependencies]
serde = "1.0"
tokio = "1.35"
# Dans mon_app/Cargo.toml :
[dependencies]
ma_lib = { path = "../ma_lib" }
serde = { workspace = true }
cargo build              # Build tout le workspace
cargo test                # Test tout le workspace
cargo build -p mon_app    # Build une crate spécifique

Avantages des workspaces

  • Dépendances partagées (une seule version résolue).
  • Build et test unifiés.
  • Facilite le développement de projets multi-crates.

7. Best practices

  1. Organisation claire — un module = une responsabilité, éviter les modules fourre-tout.
  2. API publique minimale — n'exposer que ce qui est nécessaire avec pub.
  3. Re-exports stratégiques — simplifier l'API avec pub use dans lib.rs.
  4. Documentation — documenter tout ce qui est pub avec //!.
  5. Tests à côté du code — utiliser #[cfg(test)] pour les tests unitaires.
  6. Séparation lib/bin — la logique dans lib.rs, le CLI dans main.rs.
  7. Features optionnelles — utiliser les features pour les dépendances lourdes optionnelles.

8. Exemple complet

// Structure :
// mon_api/
//   Cargo.toml
//   src/
//     lib.rs
//     models/
//       mod.rs
//       user.rs
//       post.rs
//     api/
//       mod.rs
//       routes.rs
//     database/
//       mod.rs
//       connection.rs
// Dans lib.rs :
pub mod models;
pub mod api;
mod database;  // Privé
// Re-exports pour API simple
pub use models::{User, Post};
pub use api::routes::configure_routes;
// Dans models/mod.rs :
pub mod user;
pub mod post;
// Dans models/user.rs :
#[derive(Debug)]
pub struct User {
    pub id: u32,
    pub nom: String,
}
impl User {
    pub fn new(id: u32, nom: String) -> Self {
        Self { id, nom }
    }
}
// Utilisation externe :
use mon_api::{User, configure_routes};
fn main() {
    let user = User::new(1, "Alice".to_string());
    println!("{:?}", user);
}

Conclusion

Points clés pour organiser un projet Rust :

  • Modules pour organiser le code.
  • pub pour contrôler la visibilité.
  • lib.rs pour les bibliothèques.
  • Workspaces pour les projets multi-crates.
⚡ Runique exercise Beta
Say hello to start an exercise for this course.