Contactez-nous

Structure typique d'un projet : `src/main.rs` et `src/lib.rs`

Explorez l'organisation standard des projets Rust, la signification de `src/main.rs` pour les exécutables et `src/lib.rs` pour les bibliothèques, et comment Cargo structure vos sources.

L'importance des conventions de structure en Rust

Lorsque vous débutez avec Rust, l'une des premières choses que vous remarquerez est la manière dont Cargo, l'outil de gestion de projet et de construction, organise les fichiers. Cette organisation n'est pas arbitraire ; elle suit des conventions bien établies qui facilitent la compréhension, la maintenance et la collaboration sur les projets Rust. Au coeur de cette structure se trouvent le répertoire `src` et, en son sein, les fichiers emblématiques `main.rs` et `lib.rs`. Comprendre leur rôle respectif est fondamental pour développer des applications et des bibliothèques en Rust de manière efficace.

Cette structure standardisée permet aux développeurs de naviguer rapidement dans de nouvelles bases de code, sachant où trouver les points d'entrée d'une application ou le code public d'une bibliothèque. Elle simplifie également l'outillage, car Cargo et d'autres outils comme `rust-analyzer` s'appuient sur ces conventions pour fonctionner correctement. Ce chapitre explore en détail la signification et l'utilisation de `src/main.rs` et `src/lib.rs`, ainsi que d'autres aspects de l'organisation typique d'un projet Rust.

Adopter ces conventions dès le début de votre parcours d'apprentissage en Rust vous aidera à écrire un code plus idiomatique et à vous intégrer plus facilement dans la communauté des développeurs Rust.

`src/main.rs` : le point d'entrée des applications exécutables

Si votre projet Rust est destiné à être une application exécutable (un programme que les utilisateurs peuvent lancer directement), son point d'entrée principal se trouvera, par convention, dans un fichier nommé `main.rs`, situé à l'intérieur du répertoire `src`. Ce répertoire `src` (abréviation de "source") est l'endroit où réside tout le code source de votre projet.

Le fichier `src/main.rs` doit contenir une fonction spéciale nommée `main`. C'est cette fonction `main()` qui sera appelée en premier lorsque votre programme compilé sera exécuté. Toute application Rust exécutable doit posséder une et une seule fonction `main` dans le binaire principal. La structure la plus simple d'un fichier `src/main.rs` est la suivante :

// src/main.rs
fn main() {
    // Votre code commence ici
    println!("Bonjour depuis mon application Rust !");
}

Lorsque vous créez un nouveau projet binaire avec la commande `cargo new nom_du_projet` (sans l'option `--lib`), Cargo génère automatiquement ce fichier `src/main.rs` avec un exemple "Hello, world!". C'est le squelette de base à partir duquel vous commencerez à construire votre application.

Il est important de noter qu'un projet peut avoir plusieurs fichiers binaires. Si vous avez besoin de créer d'autres exécutables au sein du même paquet (par exemple, un outil CLI principal et un utilitaire séparé), vous pouvez placer des fichiers Rust supplémentaires dans un sous-répertoire `src/bin/`. Chaque fichier `.rs` dans `src/bin/` sera compilé comme un exécutable distinct, et chacun devra avoir sa propre fonction `main()`. Par exemple, un fichier `src/bin/mon_outil.rs` serait compilé en un binaire nommé `mon_outil`.

La fonction `main` peut également retourner un type `Result<T, E>`, ce qui est une manière idiomatique en Rust de gérer les erreurs qui pourraient survenir au plus haut niveau de l'application. Par exemple :

// src/main.rs
use std::fs::File;

fn main() -> std::io::Result<()> {
    let _file = File::open("un_fichier_inexistant.txt")?;
    println!("Fichier ouvert avec succès (ce message ne s'affichera probablement pas).");
    Ok(())
}

Dans ce cas, si `File::open` retourne une erreur, le programme se terminera et affichera l'erreur sans nécessiter un bloc `match` explicite ou un `unwrap()` dans `main`.

`src/lib.rs` : le coeur des bibliothèques (crates)

Si votre projet Rust a pour but de créer une bibliothèque (une *crate* dans le jargon Rust) destinée à être utilisée comme dépendance par d'autres projets Rust (qu'ils soient des applications ou d'autres bibliothèques), alors le fichier central de votre code source sera `src/lib.rs`. Ce fichier est le point d'entrée de votre bibliothèque.

Contrairement à `src/main.rs` qui définit un exécutable, `src/lib.rs` contient le code qui expose l'API publique de votre bibliothèque. Les fonctions, structures (structs), énumérations (enums), modules, et constantes que vous souhaitez rendre accessibles aux utilisateurs de votre crate doivent être marqués avec le mot-clé `pub` (public). Les éléments non marqués `pub` sont considérés comme privés et ne sont utilisables qu'à l'intérieur de votre propre crate.

Lorsque vous créez une nouvelle bibliothèque avec `cargo new nom_de_la_bibliotheque --lib`, Cargo génère un `src/lib.rs` avec un exemple simple, incluant souvent une fonction et un module de test :

// src/lib.rs
pub fn ajouter_un(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ca_fonctionne() {
        let resultat = ajouter_un(2);
        assert_eq!(resultat, 3);
    }
}

Un paquet Cargo peut tout à fait contenir à la fois un `src/lib.rs` (définissant une bibliothèque) et un ou plusieurs exécutables (par exemple, dans `src/main.rs` ou `src/bin/`). C'est un cas d'usage courant où une crate fournit une bibliothèque principale ainsi qu'un exécutable CLI qui l'utilise. Dans ce scénario, le code de `src/main.rs` (ou des fichiers dans `src/bin/`) peut utiliser les fonctionnalités publiques définies dans `src/lib.rs` en important la crate comme s'il s'agissait d'une dépendance externe (en utilisant le nom du paquet).

Par exemple, si votre paquet s'appelle `ma_crate_utilitaire` et contient un `src/lib.rs` avec une fonction `pub fn faire_quelque_chose()`, votre `src/main.rs` pourrait l'utiliser ainsi :

// src/main.rs
use ma_crate_utilitaire::faire_quelque_chose; // Importe la fonction depuis la bibliothèque du même paquet

fn main() {
    faire_quelque_chose();
}

Cette séparation claire entre la logique de bibliothèque (réutilisable) et la logique d'application (spécifique à un exécutable) est une bonne pratique encouragée par l'écosystème Rust.

Organisation des modules et autres fichiers sources

Au-delà de `main.rs` et `lib.rs`, les projets Rust plus conséquents nécessitent une organisation du code en modules pour maintenir la clarté et la maintenabilité. Rust dispose d'un système de modules puissant qui permet de diviser le code en unités logiques. Vous pouvez définir des modules directement dans `main.rs` ou `lib.rs` en utilisant le mot-clé `mod`.

Pour les modules plus importants, il est courant de les placer dans des fichiers séparés. Il y a deux manières principales de le faire :

  • Fichier `nom_du_module.rs` : Si vous déclarez `mod mon_module;` dans `src/lib.rs` (ou `src/main.rs`), Cargo cherchera un fichier `src/mon_module.rs` et y chargera le contenu du module.
  • Répertoire `nom_du_module/mod.rs` : Alternativement, vous pouvez créer un répertoire `src/mon_module/` et y placer un fichier `mod.rs`. La déclaration `mod mon_module;` chargera alors `src/mon_module/mod.rs`. Ce fichier `mod.rs` peut ensuite déclarer d'autres sous-modules qui seraient des fichiers `.rs` dans le répertoire `src/mon_module/`. Par exemple, `mod sous_module;` dans `src/mon_module/mod.rs` chargerait `src/mon_module/sous_module.rs`.

Depuis l'édition Rust 2018, la convention préférée pour un module `mon_module` avec des sous-modules est d'avoir un fichier `src/mon_module.rs` qui déclare les sous-modules, plutôt que d'utiliser un répertoire `mon_module` avec un `mod.rs` à l'intérieur, bien que cette dernière structure soit toujours supportée.

Autres répertoires et fichiers conventionnels que vous pourriez rencontrer :

  • tests/ : Contient les tests d'intégration. Chaque fichier `.rs` dans ce répertoire est compilé comme une crate de test distincte.
  • examples/ : Contient des exemples d'utilisation de votre bibliothèque. Chaque fichier `.rs` est un exécutable indépendant.
  • benches/ : Contient les benchmarks (tests de performance) de votre code.
  • build.rs : Un script optionnel à la racine du projet, exécuté avant la compilation de votre paquet, utile pour la génération de code ou la liaison avec des bibliothèques C.
  • Cargo.lock : Fichier généré par Cargo qui enregistre les versions exactes de toutes les dépendances. Il garantit des builds reproductibles. Il doit être commité dans votre système de gestion de version.
  • target/ : Répertoire généré par Cargo où tous les artéfacts de compilation (binaires, bibliothèques, etc.) sont stockés. Il est généralement ajouté au `.gitignore`.

En respectant cette structure de projet, vous facilitez non seulement la lecture et la maintenance de votre code, mais vous tirez également pleinement parti de l'outillage et des conventions de l'écosystème Rust, ce qui contribue à une expérience de développement plus productive et agréable.