Métaprogrammation en Rust : macros et puissance du code à un nouveau niveau

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

Introduction : pourquoi Rust sait-il faire de la magie avec le code ?

\n\n

Avez-vous déjà écrit du code en Rust en ayant l'impression de répéter les mêmes constructions pour une dizaine de structures ? Ou auriez-vous aimé générer automatiquement l'implémentation d'un trait sans copier le code manuellement ? Bienvenue dans le monde de la métaprogrammation en Rust — des techniques qui permettent d'écrire du code qui écrit du code.

\n\n

Rust ne dispose pas d'une réflexion complète à l'exécution (comme Java ou Python), mais il propose quelque chose de plus puissant et plus sûr — les macros. Elles fonctionnent au moment de la compilation, en analysant et en transformant l'arbre syntaxique abstrait (AST). Cela offre des possibilités incroyables pour optimiser et réduire le code boilerplate sans perte de performance.

\n\n

Dans cet article, nous allons explorer les techniques avancées de métaprogrammation : des classiques macro_rules! aux macros procédurales complètes. Vous apprendrez à créer vos propres attributs derive et à écrire des macros d'attribut qui modifient la sémantique du code. C'est parti !

\n\n

Macros déclaratives : macro_rules! — ce n'est pas qu'un "remplacement de texte"

\n\n

Beaucoup pensent que macro_rules! est un préprocesseur textuel primitif. En réalité, c'est un puissant système de correspondance de motifs, fonctionnant au niveau des tokens. Il permet de créer des DSL (langages dédiés) directement dans Rust.

\n\n

Exemple avancé : création d'un builder avec une macro

\n\n

Imaginez que vous deviez créer un "constructeur" (builder) pour une structure. Au lieu d'écrire des dizaines de méthodes, vous pouvez écrire une seule macro :

\n\n
macro_rules! builder {\n    ($struct_name:ident, { $($field:ident: $field_type:ty),* $(,)? }) => {\n        struct $struct_name {\n            $($field: Option<$field_type>),*\n        }\n\n        impl $struct_name {\n            $(\n                pub fn $field(mut self, value: $field_type) -> Self {\n                    self.$field = Some(value);\n                    self\n                }\n            )*\n\n            pub fn build(self) -> Result<Self, String> {\n                $(\n                    if self.$field.is_none() {\n                        return Err(format!("Le champ {} est obligatoire", stringify!($field)));\n                    }\n                )*\n                Ok(self)\n            }\n        }\n    };\n}\n\nbuilder!(Config, {\n    host: String,\n    port: u16,\n    timeout: u64,\n});\n\nfn main() {\n    let config = Config {\n        host: None,\n        port: None,\n        timeout: None,\n    }\n    .host("localhost".to_string())\n    .port(8080)\n    .build()\n    .unwrap();\n    println!("Config prête : {}:{}", config.host.unwrap(), config.port.unwrap());\n}\n
\n\n

Cette macro ne se contente pas de générer les champs, elle ajoute également une validation des champs obligatoires. Notez l'utilisation de stringify!($field) — il convertit l'identifiant en chaîne de caractères au moment de la compilation. C'est une technique typique de métaprogrammation avancée en Rust.

\n\n

Répétitions et itérations : $(...)* en action

\n\n

La syntaxe de répétition permet de traiter des listes d'arguments de n'importe quelle longueur. En combinant ident, ty, expr et d'autres "fragments", vous pouvez créer des motifs complexes. Par exemple, une macro pour créer un enum avec une implémentation automatique de Display :

\n\n
macro_rules! display_enum {\n    ($enum_name:ident, { $($variant:ident => $desc:expr),* $(,)? }) => {\n        enum $enum_name {\n            $($variant),*\n        }\n\n        impl std::fmt::Display for $enum_name {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                match self {\n                    $(Self::$variant => write!(f, "{}", $desc)),*\n                }\n            }\n        }\n    };\n}\n\ndisplay_enum!(Status, {\n    Active => "status_actif",\n    Inactive => "status_inactif",\n    Pending => "s                

Blogs

Book Recommendations