Makroer lader dig skrive kode, der skriver anden kode. Find ud af om metaprogrammerings mærkelige og magtfulde verden.
Kodegenerering er en funktion, du finder i de fleste moderne programmeringssprog. Det kan hjælpe dig med at reducere standardkode og kodeduplikering, definere domænespecifikke sprog (DSL'er) og implementere ny syntaks.
Rust giver et kraftfuldt makrosystem, der giver dig mulighed for at generere kode på kompileringstidspunktet for mere sofistikeret programmering.
Introduktion til rustmakroer
Makroer er en type metaprogrammering, som du kan bruge til at skrive kode, der skriver kode. I Rust er en makro et stykke kode, der genererer anden kode på kompileringstidspunktet.
Rustmakroer er en kraftfuld funktion, der giver dig mulighed for at skrive kode, der genererer anden kode på kompileringstidspunktet til automatisering af gentagne opgaver. Rusts makroer hjælper med at reducere kodeduplikering og øge kodevedligeholdelse og læsbarhed.
Du kan bruge makroer til at generere alt fra simple kodestykker til biblioteker og rammer. Makroer adskiller sig fra
Rustfunktioner fordi de opererer på kode i stedet for data under kørsel.Definition af makroer i rust
Du definerer makroer med makro_regler! makro. Det makro_regler! makro tager et mønster og en skabelon som input. Rust matcher mønsteret mod inputkoden og bruger skabelonen til at generere outputkoden.
Sådan kan du definere makroer i rust:
makro_regler! sig hej {
() => {
println!("Hej Verden!");
};
}
fnvigtigste() {
sig hej!();
}
Koden definerer en sig hej makro, der genererer kode til at udskrive "Hej verden!". Koden matcher () syntaks mod et tomt input og println! makro genererer outputkoden.
Her er resultatet af at køre makroen i vigtigste fungere:
Makroer kan tage input-argumenter for den genererede kode. Her er en makro, der tager et enkelt argument og genererer kode for at udskrive en besked:
makro_regler! sige_besked {
($message: expr) => {
println!("{}", $meddelelse);
};
}
Det sige_besked makro tager $besked argument og genererer kode til at udskrive argumentet ved hjælp af println! makro. Det udtr syntaks matcher argumentet mod ethvert Rust-udtryk.
Typer af rustmakroer
Rust giver tre typer makroer. Hver af makrotyperne tjener specifikke formål, og de har deres syntaks og begrænsninger.
Procedurelle makroer
Procedure makroer betragtes som den mest kraftfulde og alsidige type. Proceduremakroer giver dig mulighed for at definere brugerdefineret syntaks, der genererer rustkode samtidigt. Du kan bruge proceduremæssige makroer til at oprette tilpassede afledte makroer, tilpassede attributlignende makroer og tilpassede funktionslignende makroer.
Du vil bruge tilpassede afledte makroer til automatisk at implementere strukturer og enum-egenskaber. Populære pakker som Serde bruger en tilpasset afledt makro til at generere serialiserings- og deserialiseringskode til Rust-datastrukturer.
Brugerdefinerede attributlignende makroer er praktiske til at tilføje brugerdefinerede annoteringer til rustkode. Rocket-webrammen bruger en brugerdefineret attributlignende makro til at definere ruter kortfattet og læsbart.
Du kan bruge brugerdefinerede funktionslignende makroer til at definere nye Rust-udtryk eller -sætninger. Lazy_static-kassen bruger en brugerdefineret funktionslignende makro til at definere doven-initialiseret statiske variable.
Sådan kan du definere en proceduremæssig makro, der definerer en tilpasset afledt makro:
brug proc_macro:: TokenStream;
brug citat:: citat;
brug syn::{DeriveInput, parse_macro_input};
Det brug direktiver importerer nødvendige kasser og typer til at skrive en Rust procedure makro.
#[proc_macro_derive (MyTrait)]
pubfnmin_afledte_makro(input: TokenStream) -> TokenStream {
lade ast = parse_macro_input!(input som DeriveInput);
lade navn = &ast.ident;lade gen = citat! {
impl MyTrait til #navn {
// implementering her
}
};
gen.into()
}
Programmet definerer en proceduremæssig makro, der genererer implementeringen af en egenskab for en struktur eller enum. Programmet kalder makroen med navnet MyTrait i strukturens eller enums afledte attribut. Makroen tager en TokenStream objekt som input, der indeholder koden parset ind i et abstrakt syntakstræ (AST) med parse_macro_input! makro.
Det navn variabel er den afledte struktur eller enum identifikator, den citere! Makroen genererer en ny AST, der repræsenterer implementeringen af MyTrait for den type, der til sidst returneres som en TokenStream med ind i metode.
For at bruge makroen skal du importere makroen fra det modul, hvor du erklærede den:
// forudsat at du har erklæret makroen i et my_macro_module-modul
brug my_macro_module:: my_derive_macro;
Når du erklærer den struktur eller enum, der bruger makroen, tilføjer du #[aflede (MyTrait)] henføres til toppen af erklæringen.
#[aflede (MyTrait)]
strukturMyStruct {
// felter her
}
struct-erklæringen med attributten udvides til en implementering af MyTrait egenskab for strukturen:
impl MyTrait til MyStruct {
// implementering her
}
Implementeringen giver dig mulighed for at bruge metoder i MyTrait egenskab på MyStruct tilfælde.
Attribut makroer
Attributmakroer er makroer, som du kan anvende på Rust-elementer som strukturer, enums, funktioner og moduler. Attributmakroer har form af en attribut efterfulgt af en liste med argumenter. Makroen analyserer argumentet for at generere Rust-kode.
Du skal bruge attributmakroer til at tilføje tilpasset adfærd og annoteringer til din kode.
Her er en attributmakro, der tilføjer en brugerdefineret attribut til en Rust-struktur:
// importerer moduler til makrodefinitionen
brug proc_macro:: TokenStream;
brug citat:: citat;
brug syn::{parse_macro_input, DeriveInput, AttributeArgs};#[proc_macro_attribute]
pubfnmin_attribut_makro(attr: TokenStream, vare: TokenStream) -> TokenStream {
lade args = parse_macro_input!(attr som AttributeArgs);
lade input = parse_macro_input!(item som DeriveInput);
lade navn = &input.ident;lade gen = citat! {
#input
impl #navn {
// tilpasset adfærd her
}
};
gen.into()
}
Makroen tager en liste over argumenter og en strukturdefinition og genererer en modificeret struktur med den definerede brugerdefinerede adfærd.
Makroen tager to argumenter som input: attributten anvendt på makroen (parset med parse_macro_input! makro) og elementet (parset med parse_macro_input! makro). Makroen bruger citere! makro for at generere koden, inklusive det originale inputelement og et ekstra impl blok, der definerer den tilpassede adfærd.
Endelig returnerer funktionen den genererede kode som en TokenStream med ind i() metode.
Makro regler
Makroregler er den mest ligetil og fleksible type makroer. Makroregler giver dig mulighed for at definere brugerdefineret syntaks, der udvides til Rust-kode på kompileringstidspunktet. Makroregler definerer tilpassede makroer, der matcher ethvert rustudtryk eller -udsagn.
Du vil bruge makroregler til at generere boilerplate-kode for at abstrahere detaljer på lavt niveau.
Sådan kan du definere og bruge makroregler i dine Rust-programmer:
makro_regler! make_vector {
( $( $x: expr ),* ) => {
{
lademut v = Vec::ny();
$(
v.push($x);
)*
v
}
};
}
fnvigtigste() {
lade v = make_vector![1, 2, 3];
println!("{:?}", v); // udskriver "[1, 2, 3]"
}
Programmet definerer en make_vector! en makro, der opretter en ny vektor fra en liste over kommaseparerede udtryk i vigtigste fungere.
Inde i makroen matcher mønsterdefinitionen de argumenter, der sendes til makroen. Det $($x: expr ),* syntaks matcher alle kommaseparerede udtryk identificeret som $x.
Det $( ) syntaks i udvidelseskoden itererer over hvert udtryk i listen over argumenter, der sendes til makroen efter den afsluttende parentes, hvilket indikerer, at iterationerne skal fortsætte, indtil makroen behandler alle udtryk.
Organiser dine rustprojekter effektivt
Rustmakroer forbedrer kodeorganiseringen ved at give dig mulighed for at definere genanvendelige kodemønstre og abstraktioner. Makroer kan hjælpe dig med at skrive mere kortfattet, udtryksfuld kode uden duplikationer på tværs af forskellige projektdele.
Du kan også organisere Rust-programmer i kasser og moduler for bedre kodeorganisering, genbrugelighed og interoperation med andre kasser og moduler.