Les modules en Rust
Jusqu’ici, tous les exemples tenaient dans un seul fichier main.rs. C’est très bien pour de petits programmes, mais les vrais projets découpent leur code en morceaux logiques. En Rust, l’outil pour ça est le système de modules : mod, pub et use.
Définir un module
Section intitulée « Définir un module »Un module est un bloc nommé qui regroupe du code qui va ensemble :
mod math { pub fn add(a: i32, b: i32) -> i32 { a + b }}
fn main() { let sum = math::add(2, 3); println!("{sum}");}Sortie :
5Deux choses à remarquer. D’abord, on appelle la fonction avec math::add(...), en utilisant le nom du module comme chemin. Ensuite, elle est marquée pub : sans ce mot-clé, on ne pourrait pas l’atteindre depuis l’extérieur du module.
Tout est privé par défaut
Section intitulée « Tout est privé par défaut »Ce point mérite d’être souligné, car il surprend souvent : le contenu d’un module est privé par défaut. Si vous retirez pub de l’exemple ci-dessus, le code ne compile plus :
mod math { fn add(a: i32, b: i32) -> i32 { // pas de `pub` a + b }}
fn main() { let sum = math::add(2, 3); // ❌ erreur : function `add` is private println!("{sum}");}Le compilateur répond « function add is private » : la fonction add est privée. Tout ce que vous voulez rendre utilisable en dehors du module doit être marqué pub explicitement. C’est voulu : un module peut ainsi cacher ses fonctions d’aide internes et n’exposer que les parties qui forment son API publique.
Les modules imbriqués
Section intitulée « Les modules imbriqués »Un module peut contenir d’autres modules. C’est comme ça que les grands projets construisent une structure en arbre :
mod shapes { pub mod circle { pub fn area(radius: f64) -> f64 { 3.14159 * radius * radius } }}
fn main() { let area = shapes::circle::area(2.0); println!("{area}");}use : raccourcir les chemins trop longs
Section intitulée « use : raccourcir les chemins trop longs »Écrire shapes::circle::area(...) à chaque fois devient vite pénible. use fait entrer un chemin dans la portée (scope) pour que vous puissiez l’utiliser directement :
mod shapes { pub mod circle { pub fn area(radius: f64) -> f64 { 3.14159 * radius * radius } }}
use shapes::circle;
fn main() { let area = circle::area(2.0); // plus court que shapes::circle::area println!("{area}");}En fait, vous avez déjà utilisé use : c’est le même mot-clé que sur la page HashMap (use std::collections::HashMap;).
Répartir les modules dans plusieurs fichiers
Section intitulée « Répartir les modules dans plusieurs fichiers »Les exemples ci-dessus mettent tout dans un seul fichier, car le Rust Playground l’exige. Dans un vrai projet sur votre machine, chaque module a en général son propre fichier. Un projet avec un module math ressemblerait typiquement à ceci :
src/├── main.rs└── math.rsmod math; // dit à Rust : le code de ce module se trouve dans math.rs
fn main() { let sum = math::add(2, 3); println!("{sum}");}pub fn add(a: i32, b: i32) -> i32 { a + b}mod math; (avec un point-virgule et sans corps) dit à Rust de charger le contenu du module depuis le fichier math.rs du même dossier. C’est le schéma que vous utiliserez dès qu’un projet dépassera un seul fichier.
Exercices
Section intitulée « Exercices »1. Un module de conversion de températures
Section intitulée « 1. Un module de conversion de températures »Créez un module convert avec deux fonctions publiques : c_to_f (Celsius vers Fahrenheit) et f_to_c (dans l’autre sens). Depuis main, affichez 100°C font 212°F et 212°F font 100°C.
Indice : les formules sont c * 9.0 / 5.0 + 32.0 et (f - 32.0) * 5.0 / 9.0. N’oubliez pas pub, et appelez les fonctions avec le nom du module, par exemple convert::c_to_f(100.0).
Voir la solution
mod convert { pub fn c_to_f(c: f64) -> f64 { c * 9.0 / 5.0 + 32.0 }
pub fn f_to_c(f: f64) -> f64 { (f - 32.0) * 5.0 / 9.0 }}
fn main() { println!("100°C font {}°F", convert::c_to_f(100.0)); println!("212°F font {}°C", convert::f_to_c(212.0));}Sortie :
100°C font 212°F212°F font 100°CLes deux fonctions sont pub, donc main (qui se trouve en dehors du module) peut les appeler. Retirez un pub et le compilateur vous dira que cette fonction est privée.
2. Une fonction d’aide privée
Section intitulée « 2. Une fonction d’aide privée »Créez un module imbriqué school::grades avec une fonction publique letter(score: u32) -> char qui renvoie 'A' à partir de 90, 'B' à partir de 80, et 'C' sinon. Dans grades, écrivez aussi une fonction d’aide privée is_at_least(score: u32, min: u32) -> bool que letter utilise. Faites entrer school::grades dans la portée avec use, puis affichez la lettre pour 95, 85 et 70.
Indice : le mod grades à l’intérieur de school et la fn letter ont tous deux besoin de pub, mais pas is_at_least. Le code d’un module peut toujours appeler les fonctions privées de ce même module.
Voir la solution
mod school { pub mod grades { pub fn letter(score: u32) -> char { if is_at_least(score, 90) { 'A' } else if is_at_least(score, 80) { 'B' } else { 'C' } }
// privée : seul le code de `grades` peut l'appeler fn is_at_least(score: u32, min: u32) -> bool { score >= min } }}
use school::grades;
fn main() { for score in [95, 85, 70] { println!("{score} -> {}", grades::letter(score)); }}Sortie :
95 -> A85 -> B70 -> Cletter peut appeler is_at_least parce qu’elles vivent dans le même module. Depuis main, grades::is_at_least(95, 90) échouerait avec « function is_at_least is private ». C’est tout l’intérêt : le module montre une petite API publique et garde ses fonctions d’aide pour lui.
Récapitulatif
Section intitulée « Récapitulatif »mod nom { ... }regroupe du code lié dans un module nommé.- Tout ce qui se trouve dans un module est privé par défaut : ajoutez
pubpour l’exposer à l’extérieur. usefait entrer un chemin dans la portée, pour ne pas avoir à l’écrire en entier à chaque fois.- Dans un projet à plusieurs fichiers,
mod nom;(sans corps) charge le code du module depuis le fichier correspondant. - Le code d’un module peut toujours utiliser les éléments privés de ce module : gardez les fonctions d’aide privées et n’exposez que ce dont les appelants ont besoin.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Par défaut, les éléments d'un mod sont-ils publics ou privés ?
2. Comment dire à Rust de charger le code d'un module depuis math.rs au lieu de l'écrire directement ?
Score : 0 / 2
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Managing Growing Projects with Packages, Crates, and Modules — The Rust Book (en anglais) : le chapitre officiel sur les paquets, les crates et les modules, et la façon dont ils s’articulent.
- Defining Modules to Control Scope and Privacy — The Rust Book (en anglais) : un exemple complet d’arbre de modules, avec un aide-mémoire pratique des règles.
- Separating Modules into Different Files — The Rust Book (en anglais) : l’organisation en plusieurs fichiers plus en détail, y compris les dossiers pour les modules imbriqués.
- Modules — Rust by Example (en anglais) : des exemples exécutables sur la visibilité,
use,superetself. - Visibility and privacy — The Rust Reference (en anglais) : les règles précises, y compris
pub(crate)pour « public seulement à l’intérieur de ma crate ».