Makroer lar deg skrive kode som skriver annen kode. Finn ut om metaprogrammerings merkelige og kraftige verden.

Kodegenerering er en funksjon du finner i de fleste moderne programmeringsspråk. Det kan hjelpe deg med å redusere standardkode og kodeduplisering, definere domenespesifikke språk (DSL) og implementere ny syntaks.

Rust gir et kraftig makrosystem som lar deg generere kode på kompileringstidspunktet for mer sofistikert programmering.

Introduksjon til rustmakroer

Makroer er en type metaprogrammering som du kan bruke for å skrive kode som skriver kode. I Rust er en makro et stykke kode som genererer annen kode på kompileringstidspunktet.

Rustmakroer er en kraftig funksjon som lar deg skrive kode som genererer annen kode på kompileringstidspunktet for å automatisere repeterende oppgaver. Rusts makroer bidrar til å redusere kodeduplisering og øke kodens vedlikeholdbarhet og lesbarhet.

Du kan bruke makroer til å generere alt fra enkle kodebiter til biblioteker og rammeverk. Makroer skiller seg fra Rustfunksjoner fordi de opererer på kode i stedet for data under kjøring.

Definere makroer i rust

Du vil definere makroer med makro_regler! makro. De makro_regler! makro tar et mønster og en mal som input. Rust matcher mønsteret mot inngangskoden og bruker malen til å generere utgangskoden.

Slik kan du definere makroer i rust:

makro_regler! si hei {
() => {
println!("Hei Verden!");
};
}

fnhoved-() {
si hei!();
}

Koden definerer en si hei makro som genererer kode for å skrive ut "Hallo, verden!". Koden samsvarer med () syntaks mot en tom inngang og println! makro genererer utgangskoden.

Her er resultatet av å kjøre makroen i hoved- funksjon:

Makroer kan ta input-argumenter for den genererte koden. Her er en makro som tar et enkelt argument og genererer kode for å skrive ut en melding:

makro_regler! si_melding {
($message: expr) => {
println!("{}", $melding);
};
}

De si_melding makro tar $melding argument og genererer kode for å skrive ut argumentet ved å bruke println! makro. De uttr syntaks samsvarer med argumentet mot et hvilket som helst Rust-uttrykk.

Typer rustmakroer

Rust gir tre typer makroer. Hver av makrotypene tjener spesifikke formål, og de har sin syntaks og begrensninger.

Prosedyremakroer

Prosedyremakroer anses som den kraftigste og mest allsidige typen. Prosedyremakroer lar deg definere egendefinert syntaks som genererer Rust-kode samtidig. Du kan bruke prosedyremakroer til å lage egendefinerte avledede makroer, egendefinerte attributtlignende makroer og egendefinerte funksjonslignende makroer.

Du vil bruke egendefinerte avledede makroer for å implementere strukturer og opptegningsegenskaper automatisk. Populære pakker som Serde bruker en egendefinert avledet makro for å generere serialiserings- og deserialiseringskode for Rust-datastrukturer.

Egendefinerte attributtlignende makroer er nyttige for å legge til egendefinerte merknader til rustkode. Rocket-nettverket bruker en egendefinert attributtlignende makro for å definere ruter kortfattet og lesbart.

Du kan bruke egendefinerte funksjonslignende makroer for å definere nye Rust-uttrykk eller -setninger. Lazy_static-kassen bruker en egendefinert funksjonslignende makro for å definere lat-initialisert statiske variabler.

Slik kan du definere en prosedyremakro som definerer en egendefinert avledet makro:

bruk proc_macro:: TokenStream;
bruk sitat:: sitat;
bruk syn::{DeriveInput, parse_macro_input};

De bruk direktiver importerer nødvendige kasser og typer for å skrive en Rust-prosessmakro.

#[proc_macro_derive (MyTrait)]
pubfnmin_avledede_makro(inndata: TokenStream) -> TokenStream {
la ast = parse_macro_input!(input som DeriveInput);
la navn = &ast.ident;

la gen = sitat! {
impl MyTrait til #Navn {
// implementering her
}
};

gen.into()
}

Programmet definerer en prosedyremakro som genererer implementeringen av en egenskap for en struktur eller enum. Programmet påkaller makroen med navnet MyTrait i derive-attributtet til strukturen eller enum. Makroen tar en TokenStream objekt som input som inneholder koden parset inn i et abstrakt syntakstre (AST) med parse_macro_input! makro.

De Navn variabel er den avledede struktur- eller enum-identifikatoren, den sitat! Makroen genererer en ny AST som representerer implementeringen av MyTrait for typen som til slutt blir returnert som en TokenStream med inn i metode.

For å bruke makroen, må du importere makroen fra modulen du erklærte den i:

// forutsatt at du erklærte makroen i en my_macro_module-modul

bruk min_makromodul:: min_derive_makro;

Når du erklærer strukturen eller enumen som bruker makroen, legger du til #[avlede (MyTrait)] tilskrives toppen av erklæringen.

#[avlede (MyTrait)]
strukturMyStruct {
// felt her
}

Strukturdeklarasjonen med attributtet utvides til en implementering av MyTrait egenskap for strukturen:

impl MyTrait til MyStruct {
// implementering her
}

Implementeringen lar deg bruke metoder i MyTrait egenskap på MyStruct forekomster.

Attributtmakroer

Attributtmakroer er makroer som du kan bruke på Rust-elementer som strukturer, enums, funksjoner og moduler. Attributtmakroer har form av et attributt etterfulgt av en liste med argumenter. Makroen analyserer argumentet for å generere Rust-kode.

Du bruker attributtmakroer for å legge til tilpasset atferd og merknader til koden din.

Her er en attributtmakro som legger til et tilpasset attributt til en Rust-struktur:

// importere moduler for makrodefinisjonen
bruk proc_macro:: TokenStream;
bruk sitat:: sitat;
bruk syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribute]
pubfnmin_attributtmakro(attr: TokenStream, vare: TokenStream) -> TokenStream {
la args = parse_macro_input!(attr som AttributeArgs);
la input = parse_macro_input!(element som DeriveInput);
la navn = &input.ident;

la gen = sitat! {
#inngang
impl #Navn {
// tilpasset oppførsel her
}
};

gen.into()
}

Makroen tar en liste over argumenter og en strukturdefinisjon og genererer en modifisert struktur med den definerte tilpassede virkemåten.

Makroen tar to argumenter som input: attributtet som brukes på makroen (parset med parse_macro_input! makro) og elementet (parset med parse_macro_input! makro). Makroen bruker sitat! makro for å generere koden, inkludert det originale inndataelementet og en ekstra impl blokk som definerer den tilpassede oppførselen.

Til slutt returnerer funksjonen den genererte koden som en TokenStream med inn i() metode.

Makro regler

Makroregler er den mest enkle og fleksible typen makroer. Makroregler lar deg definere egendefinert syntaks som utvides til Rust-kode på kompileringstidspunktet. Makroregler definerer egendefinerte makroer som samsvarer med alle rustuttrykk eller -setninger.

Du vil bruke makroregler for å generere standardkode for å abstrahere detaljer på lavt nivå.

Slik kan du definere og bruke makroregler i Rust-programmene dine:

makro_regler! make_vector {
( $( $x: expr ),* ) => {
{
lamut v = Vec::ny();
$(
v.push($x);
)*
v
}
};
}

fnhoved-() {
la v = make_vector![1, 2, 3];
println!("{:?}", v); // skriver ut "[1, 2, 3]"
}

Programmet definerer en make_vector! en makro som lager en ny vektor fra en liste med kommaseparerte uttrykk i hoved- funksjon.

Inne i makroen samsvarer mønsterdefinisjonen med argumentene som sendes til makroen. De $($x: expr ),* syntaks samsvarer med alle kommaseparerte uttrykk identifisert som $x.

De $( ) syntaks i utvidelseskoden itererer over hvert uttrykk i listen over argumenter som sendes til makroen etter den avsluttende parentesen, som indikerer at iterasjonene skal fortsette til makroen behandler alle uttrykkene.

Organiser rustprosjektene dine effektivt

Rustmakroer forbedrer kodeorganiseringen ved å la deg definere gjenbrukbare kodemønstre og abstraksjoner. Makroer kan hjelpe deg med å skrive mer konsis, uttrykksfull kode uten dupliseringer på tvers av ulike prosjektdeler.

Du kan også organisere Rust-programmer i kasser og moduler for bedre kodeorganisering, gjenbrukbarhet og interoperasjon med andre kasser og moduler.