ProgrammazioneArchitetto di applicazioni/biblioteche Rust

Cosa sono i macro in Rust? Tipi di macro, differenze tra procedural e declarative, quando è meglio scegliere uno dei due e quali pericoli possono sorgere nell'uso di essi?

Supera i colloqui con l'assistente IA Hintsage

Risposta

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 dichiarative (macro_rules!): sono definite utilizzando una sintassi simile a match, funzionano secondo il principio template->sostituzione. Il tipo più comune è macro_rules!.
  • Macro procedurali: sono dichiarate come funzioni esterne nel crate, ricevono l'AST (TokenStream) e restituiscono codice modificato. Si dividono in #[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.

Domanda trabocchetto

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 } }

Esempi di errori reali dovuti alla mancanza di conoscenza delle sottigliezze dell'argomento


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à.