In Rust, i macro permettono di generare codice durante la fase di compilazione, fornendo potenti strumenti per la metaprogrammazione, riduzione del boilerplate e implementazione di DSL. I principali tipi di macro:
macro_rules!.#[derive], attributive (#[some_macro]) e simili a funzione (custom_macro!()).Le dichiarative sono più semplici da usare per il codice template, le procedurali offrono maggiore controllo sulla sintassi e sull'analisi dei token.
Esempio di macro dichiarativa:
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>
Esempio di macro procedurale (derive):
#[derive(Debug, Clone)] struct MyStruct; // il derive Debug è implementato come macro procedurale in std.
I macro di Rust possono generare codice sintatticamente errato o codice con errori di runtime?
Risposta: Sì, i macro non verificano la correttezza dell'espansione nel momento in cui sono scritti; gli errori possono manifestarsi solo dopo che il macro è stato sostituito dal compilatore. I macro dichiarativi possono portare a errori sintattici poco chiari. I macro procedurali possono generare codice errato o vulnerabile, quindi è fondamentale testare attentamente il loro funzionamento.
Esempio:
macro_rules! make_error { () => { let x = ; // l'errore di sintassi si presenterà all'uso del macro } }
Storia
In un grande progetto, per ridurre il boilerplate sono stati usati macro_rules!, non coprendo tutti i casi dei pattern. Un utente ha accidentalmente passato un'espressione non supportata al macro, causando un errore di compilazione difficile da rintracciare.
Storia
Trasferendo i macro procedurali tra crate sono emersi problemi di incompatibilità delle versioni dell'API TokenStream, causando il blocco dell'IDE e l'errore si è manifestato solo nelle build no_std.
Storia
Scrivendo un DSL per la configurazione con un macro procedurale, è stata implementata un'analisi non sicura dei token in ingresso (senza validazione dei tipi), causando strani bug, vulnerabilità e l'impossibilità di distribuire correttamente parte della nuova funzionalità.