Makra umożliwiają pisanie kodu, który zapisuje inny kod. Poznaj dziwny i potężny świat metaprogramowania.

Generowanie kodu to funkcja, którą można znaleźć w większości nowoczesnych języków programowania. Może pomóc zredukować kod szablonowy i powielanie kodu, zdefiniować języki specyficzne dla domeny (DSL) i zaimplementować nową składnię.

Rust zapewnia potężny system makr, który pozwala generować kod w czasie kompilacji dla bardziej wyrafinowanego programowania.

Wprowadzenie do makr Rust

Makra to rodzaj metaprogramowania, który można wykorzystać do napisania kodu, który zapisuje kod. W Rust makro jest fragmentem kodu, który generuje inny kod w czasie kompilacji.

Makra Rust to potężna funkcja, która pozwala pisać kod generujący inny kod w czasie kompilacji w celu automatyzacji powtarzalnych zadań. Makra Rust pomagają zredukować powielanie kodu oraz zwiększyć łatwość konserwacji i czytelność kodu.

Możesz używać makr do generowania wszystkiego, od prostych fragmentów kodu po biblioteki i frameworki. Makra różnią się od

instagram viewer
Funkcje rdzy ponieważ działają na kodzie, a nie na danych w czasie wykonywania.

Definiowanie makr w Rust

Makra zdefiniujesz za pomocą makro_reguły! makro. The makro_reguły! makro pobiera wzorzec i szablon jako dane wejściowe. Rust dopasowuje wzorzec do kodu wejściowego i używa szablonu do generowania kodu wyjściowego.

Oto jak możesz zdefiniować makra w Rust:

makro_reguły! Powiedz cześć {
() => {
drukuj!("Witaj świecie!");
};
}

przypgłówny() {
Powiedz cześć!();
}

Kodeks definiuje A Powiedz cześć makro, które generuje kod do wyświetlenia „Witaj, świecie!”. Kod pasuje do () składnia przeciwko pustemu wejściu i drukuj! makro generuje kod wyjściowy.

Oto wynik uruchomienia makra w pliku główny funkcjonować:

Makra mogą pobierać argumenty wejściowe dla wygenerowanego kodu. Oto makro, które przyjmuje pojedynczy argument i generuje kod do wydrukowania wiadomości:

makro_reguły! powiedz_wiadomość {
($wiadomość: wyrażenie) => {
drukuj!("{}", $wiadomość);
};
}

The powiedz_wiadomość makro bierze $wiadomość argument i generuje kod do wydrukowania argumentu za pomocą metody drukuj! makro. The wyr składnia pasuje do argumentu przeciwko dowolnemu wyrażeniu Rusta.

Rodzaje makr rdzy

Rust udostępnia trzy typy makr. Każdy z typów makr służy określonym celom i ma swoją składnię oraz ograniczenia.

Makra proceduralne

Makra proceduralne są uważane za najpotężniejszy i najbardziej wszechstronny typ. Makra proceduralne pozwalają zdefiniować niestandardową składnię, która jednocześnie generuje kod Rusta. Makr proceduralnych można używać do tworzenia niestandardowych makr wyprowadzania, niestandardowych makr podobnych do atrybutów i niestandardowych makr podobnych do funkcji.

Użyjesz niestandardowych makr wyprowadzania, aby automatycznie zaimplementować struktury i cechy wyliczeniowe. Popularne pakiety, takie jak Serde, używają niestandardowego makra pochodnego do generowania kodu serializacji i deserializacji dla struktur danych Rust.

Niestandardowe makra podobne do atrybutów są przydatne do dodawania niestandardowych adnotacji do kodu Rust. Platforma internetowa Rocket wykorzystuje niestandardowe makro podobne do atrybutów do zwięzłego i czytelnego definiowania tras.

Możesz użyć niestandardowych makr podobnych do funkcji, aby zdefiniować nowe wyrażenia lub instrukcje Rusta. Skrzynia Lazy_static używa niestandardowego makra podobnego do funkcji do zdefiniowania leniwie zainicjowany zmienne statyczne.

Oto jak zdefiniować makro proceduralne, które definiuje niestandardowe makro pochodne:

używać proc_macro:: TokenStream;
używać cytat:: cytat;
używać syn::{DeriveInput, parse_macro_input};

The używać dyrektywy importują skrzynki i typy niezbędne do napisania makra proceduralnego Rusta.

#[proc_macro_derive (Moja Cecha)]
pubprzypmoje_pochodne_makro(wejście: TokenStream) -> TokenStream {
pozwalać ast = parse_macro_input!(input Jak Wyprowadź dane wejściowe);
pozwalać nazwa = &ast.ident;

pozwalać gen = cytat! {
implik Moja Cecha Do #nazwa {
// implementacja tutaj
}
};

gen.do()
}

Program definiuje makro proceduralne, które generuje implementację cechy dla struktury lub wyliczenia. Program wywołuje makro o podanej nazwie Moja Cecha w atrybucie pochodne struktury lub wyliczenia. Makro zajmuje ok TokenStream obiekt jako dane wejściowe zawierające kod przeanalizowany w abstrakcyjne drzewo składniowe (AST) za pomocą parse_macro_input! makro.

The nazwa zmienna jest pochodną strukturą lub identyfikatorem enum, the cytat! Makro generuje nowy AST reprezentujący implementację Moja Cecha dla typu, który ostatecznie jest zwracany jako a TokenStream z do metoda.

Aby użyć makra, musisz zaimportować makro z modułu, w którym je zadeklarowałeś:

// zakładając, że zadeklarowałeś makro w module my_macro_module

używać my_macro_module:: my_derive_macro;

Deklarując strukturę lub wyliczenie korzystające z makra, dodasz metodę #[wyprowadź (Moja Cecha)] atrybut na początku deklaracji.

#[wyprowadź (Moja Cecha)]
strukturaMojaStruktura {
// pola tutaj
}

Deklaracja struktury z atrybutem rozwija się do implementacji Moja Cecha cecha dla struktury:

implik Moja Cecha Do MojaStruktura {
// implementacja tutaj
}

Implementacja pozwala na wykorzystanie metod w Moja Cecha cecha na MojaStruktura instancje.

Makra atrybutów

Makra atrybutów to makra, które można zastosować do elementów Rusta, takich jak struktury, wyliczenia, funkcje i moduły. Makra atrybutów mają postać atrybutu, po którym następuje lista argumentów. Makro analizuje argument w celu wygenerowania kodu Rusta.

Użyjesz makr atrybutów, aby dodać niestandardowe zachowania i adnotacje do swojego kodu.

Oto makro atrybutu, które dodaje niestandardowy atrybut do struktury Rusta:

// importowanie modułów do definicji makra
używać proc_macro:: TokenStream;
używać cytat:: cytat;
używać syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribute]
pubprzypmoje_makro_atrybutu(attr: TokenStream, pozycja: TokenStream) -> TokenStream {
pozwalać args = parse_macro_input!(attr Jak AttributeArgs);
pozwalać input = parse_macro_input!(item Jak Wyprowadź dane wejściowe);
pozwalać nazwa = &input.ident;

pozwalać gen = cytat! {
#wejście
implik #nazwa {
// niestandardowe zachowanie tutaj
}
};

gen.do()
}

Makro pobiera listę argumentów i definicja struktury i generuje zmodyfikowaną strukturę ze zdefiniowanym niestandardowym zachowaniem.

Makro pobiera dwa argumenty jako dane wejściowe: atrybut zastosowany do makra (przeanalizowany z rozszerzeniem parse_macro_input! makro) i element (przeanalizowany za pomocą parse_macro_input! makro). Makro wykorzystuje tzw cytat! makro, aby wygenerować kod, w tym oryginalny element wejściowy i dodatkowy implik blok, który definiuje niestandardowe zachowanie.

Na koniec funkcja zwraca wygenerowany kod jako a TokenStream z do() metoda.

Zasady makr

Reguły makr to najprostszy i najbardziej elastyczny rodzaj makr. Reguły makr pozwalają zdefiniować niestandardową składnię, która rozwija się do kodu Rusta w czasie kompilacji. Reguły makr definiują niestandardowe makra, które pasują do dowolnego wyrażenia lub instrukcji rdzy.

Użyjesz reguł makr, aby wygenerować kod wzorcowy do abstrakcyjnych szczegółów niskiego poziomu.

Oto jak możesz zdefiniować i używać reguł makr w swoich programach Rust:

makro_reguły! make_vector {
( $( $x: wyrażenie ),* ) => {
{
pozwalaćmut v = Vec::nowy();
$(
v.push($x);
)*
w
}
};
}

przypgłówny() {
pozwalać v = make_wektor![1, 2, 3];
drukuj!("{:?}", v); // drukuje „[1, 2, 3]”
}

Program definiuje A make_vector! makro, które tworzy nowy wektor z listy wyrażeń oddzielonych przecinkami w pliku główny funkcjonować.

Wewnątrz makra definicja wzorca odpowiada argumentom przekazanym do makra. The $( $x: wyrażenie ),* składnia pasuje do wszystkich wyrażeń oddzielonych przecinkami zidentyfikowanych jako $x.

The $( ) składnia w kodzie rozszerzania iteruje po każdym wyrażeniu na liście argumentów przekazywanych do makra po nawias zamykający, wskazujący, że iteracje powinny być kontynuowane, dopóki makro nie przetworzy wszystkich wyrażenia.

Efektywnie organizuj swoje projekty Rust

Makra Rusta poprawiają organizację kodu, umożliwiając definiowanie wzorców i abstrakcji kodu wielokrotnego użytku. Makra mogą pomóc w pisaniu bardziej zwięzłego, wyrazistego kodu bez duplikatów w różnych częściach projektu.

Możesz także organizować programy Rust w skrzynki i moduły w celu lepszej organizacji kodu, ponownego użycia i współpracy z innymi skrzynkami i modułami.