Le macro ti consentono di scrivere codice che scrive altro codice. Scopri lo strano e potente mondo della metaprogrammazione.
La generazione del codice è una funzionalità che troverai nella maggior parte dei linguaggi di programmazione moderni. Può aiutarti a ridurre il codice boilerplate e la duplicazione del codice, definire linguaggi specifici del dominio (DSL) e implementare una nuova sintassi.
Rust fornisce un potente sistema di macro che consente di generare codice in fase di compilazione per una programmazione più sofisticata.
Introduzione alle macro di Rust
Le macro sono un tipo di metaprogrammazione che puoi sfruttare per scrivere codice che scrive codice. In Rust, una macro è un pezzo di codice che genera altro codice in fase di compilazione.
Le macro di Rust sono una potente funzionalità che consente di scrivere codice che genera altro codice in fase di compilazione per automatizzare attività ripetitive. Le macro di Rust aiutano a ridurre la duplicazione del codice e ad aumentare la manutenibilità e la leggibilità del codice.
Puoi utilizzare le macro per generare qualsiasi cosa, da semplici frammenti di codice a librerie e framework. Le macro differiscono da Funzioni della ruggine perché operano sul codice piuttosto che sui dati in fase di esecuzione.
Definizione di macro in Rust
Definirai le macro con il macro_regole! macro. IL macro_regole! macro prende un modello e un modello come input. Rust confronta il modello con il codice di input e utilizza il modello per generare il codice di output.
Ecco come puoi definire le macro in Rust:
macro_regole! di Ciao {
() => {
stampa!("Ciao mondo!");
};
}
fnprincipale() {
di Ciao!();
}
Il codice definisce a di Ciao macro che genera codice per stampare "Hello, world!". Il codice corrisponde al () sintassi contro un input vuoto e il stampa! macro genera il codice di output.
Ecco il risultato dell'esecuzione della macro in principale funzione:
Le macro possono accettare argomenti di input per il codice generato. Ecco una macro che prende un singolo argomento e genera il codice per stampare un messaggio:
macro_regole! dire_messaggio {
($messaggio: expr) => {
stampa!("{}", $messaggio);
};
}
IL dire_messaggio macro prende il $messaggio argomento e genera il codice per stampare l'argomento utilizzando il stampa! macro. IL espr la sintassi confronta l'argomento con qualsiasi espressione di Rust.
Tipi di macro Rust
Rust fornisce tre tipi di macro. Ciascuno dei tipi di macro ha scopi specifici e ha la propria sintassi e limitazioni.
Macro procedurali
Le macro procedurali sono considerate il tipo più potente e versatile. Le macro procedurali consentono di definire una sintassi personalizzata che genera simultaneamente il codice Rust. È possibile utilizzare le macro procedurali per creare macro di derivazione personalizzate, macro personalizzate simili ad attributi e macro personalizzate simili a funzioni.
Utilizzerai macro di derivazione personalizzate per implementare automaticamente struct e tratti enum. Pacchetti popolari come Serde utilizzano una macro di derivazione personalizzata per generare il codice di serializzazione e deserializzazione per le strutture di dati di Rust.
Le macro personalizzate simili ad attributi sono utili per aggiungere annotazioni personalizzate al codice Rust. Il framework Web Rocket utilizza una macro personalizzata simile ad un attributo per definire i percorsi in modo conciso e leggibile.
È possibile utilizzare macro simili a funzioni personalizzate per definire nuove espressioni o istruzioni di Rust. La cassa Lazy_static utilizza una macro simile a una funzione personalizzata per definire il file pigro-inizializzato variabili statiche.
Ecco come puoi definire una macro procedurale che definisce una macro di derivazione personalizzata:
utilizzo proc_macro:: TokenStream;
utilizzo citazione:: citazione;
utilizzo syn::{DerivaInput, parse_macro_input};
IL utilizzo le direttive importano i crate ei tipi necessari per scrivere una macro procedurale di Rust.
#[proc_macro_derive (MyTrait)]
pubfnmia_deriva_macro(input: TokenStream) -> TokenStream {
permettere ast = parse_macro_input!(input COME DerivaInput);
permettere nome = &ast.ident;permettere gen = citazione! {
imp MyTrait per #nome {
// implementazione qui
}
};
gen.into()
}
Il programma definisce una macro procedurale che genera l'implementazione di un tratto per una struttura o enum. Il programma richiama la macro con il nome MyTrait nell'attributo deriva di struct o enum. La macro richiede a Tokenstream oggetto come input contenente il codice analizzato in un albero di sintassi astratta (AST) con il parse_macro_input! macro.
IL nome variabile è la struttura derivata o l'identificatore enum, the citazione! La macro genera un nuovo AST che rappresenta l'implementazione di MyTrait per il tipo che alla fine viene restituito come a Tokenstream con il in metodo.
Per utilizzare la macro, dovrai importare la macro dal modulo in cui l'hai dichiarata:
// supponendo che tu abbia dichiarato la macro in un modulo my_macro_module
utilizzo my_macro_module:: my_derive_macro;
Nel dichiarare la struttura o l'enumerazione che utilizza la macro, aggiungerai il file #[derive (MyTrait)] attributo all'inizio della dichiarazione.
#[derive (MyTrait)]
structMyStruct {
// campi qui
}
La dichiarazione struct con l'attributo si espande in un'implementazione di MyTrait tratto per la struttura:
imp MyTrait per MiaStruttura {
// implementazione qui
}
L'implementazione consente di utilizzare i metodi in MyTrait tratto su MyStruct istanze.
Macro di attributi
Le macro di attributi sono macro che puoi applicare agli elementi di Rust come struct, enum, funzioni e moduli. Le macro di attributi assumono la forma di un attributo seguito da un elenco di argomenti. La macro analizza l'argomento per generare il codice Rust.
Utilizzerai le macro degli attributi per aggiungere comportamenti e annotazioni personalizzati al tuo codice.
Ecco una macro di attributi che aggiunge un attributo personalizzato a una struttura Rust:
// importazione dei moduli per la definizione della macro
utilizzo proc_macro:: TokenStream;
utilizzo citazione:: citazione;
utilizzo syn::{parse_macro_input, DeriveInput, AttributeArgs};#[proc_macro_attribute]
pubfnmy_attribute_macro(attr: TokenStream, articolo: TokenStream) -> TokenStream {
permettere args = parse_macro_input!(attr COME AttributeArgs);
permettere input = parse_macro_input!(elemento COME DerivaInput);
permettere nome = &input.ident;permettere gen = citazione! {
#ingresso
imp #nome {
// comportamento personalizzato qui
}
};
gen.into()
}
La macro accetta un elenco di argomenti e una definizione di struttura e genera una struttura modificata con il comportamento personalizzato definito.
La macro accetta due argomenti come input: l'attributo applicato alla macro (analizzato con il file parse_macro_input! macro) e l'elemento (analizzato con il file parse_macro_input! macro). La macro utilizza il citazione! macro per generare il codice, incluso l'elemento di input originale e un ulteriore imp blocco che definisce il comportamento personalizzato.
Infine, la funzione restituisce il codice generato come a Tokenstream con il in() metodo.
Regole Macro
Le regole macro sono il tipo di macro più semplice e flessibile. Le regole macro ti consentono di definire una sintassi personalizzata che si espande al codice Rust in fase di compilazione. Le regole macro definiscono macro personalizzate che corrispondono a qualsiasi espressione o istruzione ruggine.
Utilizzerai le regole macro per generare codice boilerplate per astrarre dettagli di basso livello.
Ecco come puoi definire e usare le macro regole nei tuoi programmi Rust:
macro_regole! crea_vettore {
( $( $x: espr ),* ) => {
{
permetteremut v = Vec::nuovo();
$(
v.push($x);
)*
v
}
};
}
fnprincipale() {
permettere v = crea_vettore![1, 2, 3];
stampa!("{:?}", V); // stampa "[1, 2, 3]"
}
Il programma definisce a crea_vettore! una macro che crea un nuovo vettore da un elenco di espressioni separate da virgole nel file principale funzione.
All'interno della macro, la definizione del modello corrisponde agli argomenti passati alla macro. IL $( $x: espr ),* la sintassi corrisponde a qualsiasi espressione separata da virgola identificata come $x.
IL $( ) la sintassi nel codice di espansione itera su ogni espressione nell'elenco di argomenti passati alla macro successiva la parentesi di chiusura, che indica che le iterazioni dovrebbero continuare fino a quando la macro elabora tutti i file espressioni.
Organizza i tuoi progetti Rust in modo efficiente
Le macro di Rust migliorano l'organizzazione del codice consentendo di definire schemi e astrazioni di codice riutilizzabili. Le macro possono aiutarti a scrivere codice più conciso ed espressivo senza duplicazioni in varie parti del progetto.
Inoltre, puoi organizzare i programmi Rust in crate e moduli per una migliore organizzazione del codice, riutilizzabilità e interoperabilità con altri crate e moduli.