Macros en Rust entre fichiers : tutoriel complet pour le développement modulaire

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Macros en Rust : comment les faire fonctionner avec les modules ?



Avez-vous déjà écrit une macro utile en Rust, puis essayé de l'utiliser dans un module voisin — pour obtenir une mystérieuse erreur du compilateur ? Si oui, vous n'êtes pas seul. La question "How do I use a macro across module files?" a recueilli des milliers de vues sur Stack Overflow, et ce n'est pas pour rien.



En Rust, les macros sont un outil puissant de métaprogrammation. Elles permettent de générer du code à la volée, d'éviter les duplications et d'écrire des DSL (langages dédiés). Mais voilà le problème : les macros ne se comportent pas comme des fonctions ordinaires. Elles ont leurs propres règles de visibilité, et les débutants butent souvent là-dessus.



Dans ce tutoriel, je vais vous montrer trois méthodes éprouvées pour organiser les macros entre fichiers modulaires. C'est parti !



Pourquoi les macros ne fonctionnent-elles pas "par défaut" ?



Avant de plonger dans la solution, comprenons la cause. En Rust, les fonctions et les types ne sont visibles par défaut que dans leur propre module (s'ils ne sont pas marqués pub). C'est la même histoire avec les macros, mais avec une nuance : pub pour les macros ne fonctionne pas comme pour les fonctions.



Prenons un exemple simple. Supposons que nous ayons un projet avec cette structure :



src/├── main.rs├── helpers/│   ├── mod.rs│   └── macros.rs


Dans macros.rs, nous avons déclaré une macro :



// src/helpers/macros.rsmacro_rules! greet {    ($name:expr) => {        println!("Bonjour, {} !", $name);    };}


Maintenant, nous voulons l'utiliser dans main.rs :



// src/main.rsmod helpers;

fn main() { greet!("Monde"); // Erreur ! La macro n'est pas visible.}


Le compilateur affichera quelque chose comme : error: macro `greet` is not imported. Pourquoi ? Parce que les macros ne sont pas exportées par défaut depuis un module, même si le module est déclaré comme pub.



Méthode 1 : L'attribut #[macro_use] pour importer des macros



La méthode la plus simple est d'utiliser l'attribut #[macro_use] avant la déclaration du module. Cela force Rust à importer toutes les macros de ce module dans la portée actuelle.



Corrigeons notre exemple :



// src/main.rs#[macro_use]mod helpers;

fn main() { greet!("Monde"); // Maintenant ça fonctionne !}


Mais il y a un piège : #[macro_use] importe toutes les macros du module. Si vous avez beaucoup de macros, cela peut entraîner des conflits de noms. De plus, cette approche est considérée comme obsolète dans le Rust moderne (à partir de l'édition 2018).



Où est-ce encore utile ? Dans les petits projets ou lorsque vous écrivez un prototype et souhaitez connecter rapidement quelques macros sans efforts supplémentaires.



Nuance importante : l'ordre de déclaration



Si vous utilisez #[macro_use], les macros deviennent disponibles immédiatement après la déclaration du module. Cela signifie que le code suivant ne fonctionnera pas :



// src/main.rsfn use_greet() {    greet!("Test"); // Erreur ! Le module n'est pas encore déclaré.}

#[macro_use]mod helpers;

fn main() { use_greet();}


L'ordre compte. Gardez cela à l'esprit.



Méthode 2 : Exportation explicite des macros avec #[macro_export]



Une méthode plus moderne et contrôlée consiste à utiliser l'attribut #[macro_export] directement dans la définition de la macro. Il rend la macro accessible au niveau de la crate, et pas seulement du module.



Voici comment cela fonctionne :



// src/helpers/macros.rs#[macro_export]macro_rules! greet {    ($name:expr) => {        println!("Bonjour, {} !", $name);    };}


Maintenant, dans main.rs, nous pouvons importer la macro explicitement avec use :



// src/main.rsmod helpers;

use helpers::greet;

fn main() { greet!("Monde"); // Ça fonctionne !}


Notez que nous utilisons use helpers::greet, et non use hel

Blogs

Book Recommendations