ProgrammationArchitecte d'application/bibliothèque Rust

Qu'est-ce que les macros en Rust ? Types de macros, différence entre procedural et declarative, quand choisir l'un ou l'autre et quels dangers peuvent survenir lors de leur utilisation ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En Rust, les macros permettent de générer du code au moment de la compilation, fournissant des outils puissants pour la méta-programmation, la réduction du boilerplate et la mise en œuvre de DSL. Les principaux types de macros :

  • Macros déclaratives (macro_rules!) : définies à l'aide d'une syntaxe semblable à match, fonctionnent selon le principe modèle->remplacement. Le type le plus typique est macro_rules!.
  • Macros procédurales : déclarées comme des fonctions externes dans un crate, reçoivent l'AST (TokenStream) et retournent du code modifié. Elles se divisent en #[derive], attributs (#[some_macro]) et function-like (custom_macro!()).

Les macros déclaratives sont plus simples à utiliser pour du code modèle, tandis que les macros procédurales offrent plus de contrôle sur la syntaxe et l'analyse des jetons.

Exemple de macro déclarative :

macro_rules! vec_of_strings { ($($x:expr),*) => { { let mut v = Vec::new(); $(v.push($x.to_string());)* v } }; } let v = vec_of_strings!("a", "b"); // => Vec<String>

Exemple de macro procédurale (exemple derive) :

#[derive(Debug, Clone)] struct MyStruct; // le derive Debug est implémenté par une macro procédurale dans std.

Question piège

Les macros Rust peuvent-elles générer du code syntaxiquement incorrect ou du code avec des erreurs d'exécution ?

Réponse : Oui, les macros ne vérifient pas la validité des dépliements au moment de l'écriture ; les erreurs peuvent apparaître uniquement après que le compilateur a remplacé la macro. Les macros déclaratives peuvent entraîner des erreurs syntaxiques non évidentes. Les macros procédurales peuvent générer du code incorrect ou vulnérable, donc il est crucial de tester leur fonctionnement soigneusement.

Exemple :

macro_rules! make_error { () => { let x = ; // une erreur syntaxique surviendra lors de l'utilisation de la macro } }

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans un grand projet, pour réduire le boilerplate, ils ont utilisé macro_rules!, sans couvrir tous les cas de motifs. Un utilisateur a accidentellement passé une expression non supportée à la macro, ce qui a conduit à une erreur de compilation incompréhensible, dont la cause était difficile à retracer.


Histoire

Lors du transfert de macros procédurales entre les crates, des problèmes de compatibilité des versions de l'API TokenStream sont survenus, ce qui a provoqué le blocage de l'IDE, et l'erreur ne se manifestait que lors des builds no_std.


Histoire

Lors de l'écriture d'un DSL pour les configurations avec une macro procédurale, un parsing dangereux des jetons d'entrée a été réalisé (sans validation des types), ce qui a rendu apparaître des bugs étranges, des vulnérabilités et une incapacité à déployer correctement une partie des nouvelles fonctionnalités.