Met macro's kun je code schrijven die andere code schrijft. Ontdek de vreemde en krachtige wereld van metaprogrammering.

Codegeneratie is een functie die u in de meeste moderne programmeertalen aantreft. Het kan u helpen standaardcode en codeduplicatie te verminderen, domeinspecifieke talen (DSL's) te definiëren en nieuwe syntaxis te implementeren.

Rust biedt een krachtig macrosysteem waarmee u tijdens het compileren code kunt genereren voor meer geavanceerde programmering.

Inleiding tot roestmacro's

Macro's zijn een soort metaprogrammering die u kunt gebruiken om code te schrijven die code schrijft. In Rust is een macro een stuk code dat tijdens het compileren andere code genereert.

Rust-macro's zijn een krachtige functie waarmee u code kunt schrijven die tijdens het compileren andere code genereert voor het automatiseren van repetitieve taken. De macro's van Rust helpen codeduplicatie te verminderen en de onderhoudbaarheid en leesbaarheid van de code te vergroten.

U kunt macro's gebruiken om alles te genereren, van eenvoudige codefragmenten tot bibliotheken en frameworks. Macro's verschillen van

instagram viewer
Roest functies omdat ze tijdens runtime op code werken in plaats van op gegevens.

Macro's definiëren in Rust

U definieert macro's met de macro_regels! macro. De macro_regels! macro neemt een patroon en een sjabloon als invoer. Rust vergelijkt het patroon met de invoercode en gebruikt de sjabloon om de uitvoercode te genereren.

Hier ziet u hoe u macro's in Rust kunt definiëren:

macro_regels! zeg hallo {
() => {
println!("Hallo Wereld!");
};
}

fnvoornaamst() {
zeg hallo!();
}

De code definieert een zeg hallo macro die code genereert om "Hello, world!" af te drukken. De code komt overeen met de () syntaxis tegen een lege invoer en de println! macro genereert de uitvoercode.

Dit is het resultaat van het uitvoeren van de macro in de voornaamst functie:

Macro's kunnen invoerargumenten aannemen voor de gegenereerde code. Hier is een macro die een enkel argument gebruikt en code genereert om een ​​bericht af te drukken:

macro_regels! zeg_bericht {
($bericht: expr) => {
println!("{}", $bericht);
};
}

De zeg_bericht macro neemt de $ bericht argument en genereert code om het argument af te drukken met behulp van de println! macro. De uitdr syntaxis komt overeen met het argument tegen elke Rust-expressie.

Soorten roestmacro's

Rust biedt drie soorten macro's. Elk van de macrotypen heeft specifieke doelen en ze hebben hun syntaxis en beperkingen.

Procedurele macro's

Procedurele macro's worden beschouwd als het krachtigste en meest veelzijdige type. Met procedurele macro's kunt u een aangepaste syntaxis definiëren die gelijktijdig Rust-code genereert. U kunt procedurele macro's gebruiken om aangepaste afgeleide macro's, aangepaste attribuutachtige macro's en aangepaste functieachtige macro's te maken.

U gebruikt aangepaste afgeleide macro's om structs en enum-eigenschappen automatisch te implementeren. Populaire pakketten zoals Serde gebruiken een aangepaste afgeleide macro om serialisatie- en deserialisatiecode voor Rust-datastructuren te genereren.

Aangepaste attribuutachtige macro's zijn handig voor het toevoegen van aangepaste annotaties aan Rust-code. Het Rocket-webframework gebruikt een aangepaste attribuutachtige macro om routes beknopt en leesbaar te definiëren.

U kunt aangepaste functie-achtige macro's gebruiken om nieuwe Rust-uitdrukkingen of -instructies te definiëren. De Lazy_static-krat gebruikt een aangepaste functie-achtige macro om de lazy-geïnitialiseerd statische variabelen.

Zo kunt u een procedurele macro definiëren die een aangepaste afgeleide macro definieert:

gebruik proc_macro:: TokenStream;
gebruik citaat:: citaat;
gebruik syn::{DeriveInput, parse_macro_input};

De gebruik richtlijnen importeren benodigde kratten en typen voor het schrijven van een Rust procedurele macro.

#[proc_macro_derive (MyTrait)]
kroegfnmijn_afgeleide_macro(invoer: TokenStream) -> TokenStream {
laten ast = parse_macro_input!(invoer als AfleidenInvoer);
laten naam = &ast.ident;

laten gen = citaat! {
impl MijnTrait voor #naam {
// implementatie hier
}
};

gen.into()
}

Het programma definieert een procedurele macro die de implementatie van een eigenschap voor een struct of enum genereert. Het programma roept de macro met de naam aan MijnTrait in het afgeleide attribuut van de struct of enum. De macro neemt een TokenStream object als invoer met de code die is geparseerd in een Abstract Syntax Tree (AST) met de parse_macro_input! macro.

De naam variabele is de afgeleide struct of enum identifier, de citaat! De macro genereert een nieuwe AST die de implementatie vertegenwoordigt van MijnTrait voor het type dat uiteindelijk wordt geretourneerd als een TokenStream met de naar binnen methode.

Om de macro te gebruiken, moet u de macro importeren uit de module waarin u deze hebt gedeclareerd:

// ervan uitgaande dat u de macro hebt gedeclareerd in een my_macro_module module

gebruik mijn_macro_module:: mijn_afgeleide_macro;

Bij het declareren van de struct of enum die de macro gebruikt, voegt u de #[afleiden (MyTrait)] attribuut bovenaan de declaratie.

#[afleiden (MyTrait)]
structuurMijnStruct {
// velden hier
}

De struct-declaratie met het attribuut wordt uitgebreid naar een implementatie van het MijnTrait eigenschap voor de structuur:

impl MijnTrait voor MijnStruct {
// implementatie hier
}

De implementatie stelt u in staat methoden te gebruiken in de MijnTrait eigenschap op MijnStruct gevallen.

Kenmerk macro's

Attribuutmacro's zijn macro's die u kunt toepassen op Rust-items zoals structs, opsommingen, functies en modules. Attribuutmacro's hebben de vorm van een attribuut gevolgd door een lijst met argumenten. De macro ontleedt het argument om Rust-code te genereren.

U gebruikt attribuutmacro's om aangepast gedrag en annotaties aan uw code toe te voegen.

Hier is een attribuutmacro die een aangepast attribuut toevoegt aan een Rust-structuur:

// modules importeren voor de macrodefinitie
gebruik proc_macro:: TokenStream;
gebruik citaat:: citaat;
gebruik syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribuut]
kroegfnmijn_kenmerk_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
laten args = parse_macro_input!(attr als AttribuutArgs);
laten input = parse_macro_input!(item als AfleidenInvoer);
laten naam = &invoer.ident;

laten gen = citaat! {
#invoer
impl #naam {
// aangepast gedrag hier
}
};

gen.into()
}

De macro neemt een lijst met argumenten en een structuurdefinitie en genereert een gewijzigde structuur met het gedefinieerde aangepaste gedrag.

De macro neemt twee argumenten als invoer: het attribuut toegepast op de macro (geparseerd met de parse_macro_input! macro) en het item (geparseerd met de parse_macro_input! macro). De macro gebruikt de citaat! macro om de code te genereren, inclusief het originele invoeritem en een extra impl blok dat het aangepaste gedrag definieert.

Ten slotte retourneert de functie de gegenereerde code als een TokenStream met de naar binnen() methode.

Macro-regels

Macroregels zijn het meest eenvoudige en flexibele type macro's. Met macroregels kunt u aangepaste syntaxis definiëren die tijdens het compileren wordt uitgebreid naar Rust-code. Macroregels definiëren aangepaste macro's die overeenkomen met elke roestuitdrukking of statement.

U gebruikt macroregels om standaardcode te genereren om details op laag niveau te abstraheren.

Hier ziet u hoe u macroregels in uw Rust-programma's kunt definiëren en gebruiken:

macro_regels! make_vector {
($( $x: expr),*) => {
{
latenmuts v = Vec::nieuw();
$(
v.push($x);
)*
v
}
};
}

fnvoornaamst() {
laten v = maak_vector![1, 2, 3];
println!("{:?}", v); // drukt "[1, 2, 3]" af
}

Het programma definieert een maak_vector! een macro die een nieuwe vector maakt uit een lijst met door komma's gescheiden uitdrukkingen in de voornaamst functie.

Binnen de macro komt de patroondefinitie overeen met de argumenten die aan de macro zijn doorgegeven. De $( $x: expr ),* syntaxis komt overeen met alle door komma's gescheiden uitdrukkingen geïdentificeerd als $x.

De $( ) syntaxis in de uitbreidingscode herhaalt elke uitdrukking in de lijst met argumenten die daarna aan de macro worden doorgegeven het haakje sluiten, wat aangeeft dat de iteraties moeten doorgaan totdat de macro alle uitdrukkingen.

Organiseer uw roestprojecten efficiënt

Rust-macro's verbeteren de codeorganisatie doordat u herbruikbare codepatronen en abstracties kunt definiëren. Macro's kunnen u helpen bij het schrijven van meer beknopte, expressieve code zonder duplicaties tussen verschillende projectonderdelen.

U kunt Rust-programma's ook organiseren in kratten en modules voor een betere codeorganisatie, herbruikbaarheid en samenwerking met andere kratten en modules.