Makra proceduralne: Derive, Function
Poznają Państwo makra proceduralne, w tym własne makra `#[derive]` i makra funkcyjne, służące do bardziej złożonego generowania kodu.
Makra proceduralne: Derive, Function to bezpłatna lekcja Learn Rust Coding na CoddyKit. To lekcja 2 z 3. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Learn Rust Coding, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Learn Rust Coding zawiera 3 lekcji w sumie.
Wprowadzenie do makr proceduralnych
Witamy w świecie makr proceduralnych! W przeciwieństwie do makr deklaratywnych (macro_rules!) makra proceduralne przypominają funkcje operujące bezpośrednio na kodzie Rust.
Przyjmują jako dane wejściowe TokenStream, przetwarzają je i zwracają kolejny TokenStream. Umożliwia to znacznie bardziej złożone generowanie kodu.
Rodzaje makr proceduralnych
Rust oferuje trzy główne rodzaje makr proceduralnych:
- Makra funkcyjne: Wyglądają i działają jak zwykłe wywołania funkcji, np.
my_macro!(...). - Makra derive: Umożliwiają automatyczne implementowanie traitów dla struktur i enumeracji za pomocą
#[derive(MyTrait)]. - Makra atrybutów: Umożliwiają definiowanie niestandardowych atrybutów, które można stosować do elementów, np.
#[route("/path")].
Crate `proc-macro`
Makra proceduralne muszą znajdować się w osobnym, specjalnym rodzaju crate'a. W pliku Cargo.toml deklaruje się go w następujący sposób:
Informuje to Cargo, że crate zawiera makra przekształcające kod w czasie kompilacji.
[package]
name = "my_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = trueMakra funkcyjne — wyjaśnienie
Makra funkcyjne definiuje się za pomocą atrybutu #[proc_macro] umieszczonego przy funkcji, która przyjmuje proc_macro::TokenStream i zwraca taki sam typ.
Są przydatne do tworzenia niestandardowych DSL-i (języków dziedzinowych) lub złożonego powtarzania kodu, z którym macro_rules! nie potrafi sobie poradzić.
use proc_macro::TokenStream;
#[proc_macro]
pub fn my_function_macro(input: TokenStream) -> TokenStream {
// Logic to transform input TokenStream
// ...
input // For example, return the input unchanged
}Przykład makra funkcyjnego
Oto przykład poglądowy. Makro `greet_name!` generuje instrukcję wypisywania. Rzeczywista implementacja analizowałaby wejściowy TokenStream, aby wyodrębnić nazwę.
Choć definicja makra jest złożona, jego użycie jest proste i daje duże możliwości:
/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;
#[proc_macro]
pub fn greet_name(input: TokenStream) -> TokenStream {
// Imagine parsing 'input' to get a name like 'World'
// and generating: println!("Hello, World!");
"println!(\"Hello, {}!\", \"CoddyKit\");".parse().unwrap()
}
/* In 'my_app/src/main.rs' */
use my_macros::greet_name;
fn main() {
greet_name!("CoddyKit");
}Zrozumienie makr `#[derive]`
Makra derive są najczęściej używanym rodzajem makr. Umożliwiają automatyczne implementowanie traitu dla struktury lub enumeracji.
Po zapisaniu #[derive(Debug)] makro uruchamia się w czasie kompilacji i generuje kod niezbędny do zaimplementowania przez dany typ traitu Debug.
Implementowanie `#[derive]` (koncepcja)
Niestandardowe makra derive zazwyczaj tworzy się z użyciem bibliotek takich jak syn (do analizowania kodu Rust i przekształcania go w AST) oraz quote (do generowania kodu Rust na podstawie AST).
Należy zdefiniować funkcję opatrzoną adnotacją #[proc_macro_derive(MyTrait)], która przyjmuje TokenStream reprezentujący strukturę lub enumerację i zwraca wygenerowaną implementację traitu.
/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote;
#[proc_macro_derive(MyDebug)] // MyDebug is the trait name
pub fn my_debug_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let expanded = quote! {
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!(#name)).finish()
}
}
};
expanded.into()
}Przykład użycia `#[derive(Debug)]`
Przyjrzyjmy się praktycznemu przykładowi makra derive, którego prawdopodobnie już Państwo używali: Debug. Dodając #[derive(Debug)] do struktury Point, Rust automatycznie implementuje trait Debug, co pozwala wygodnie wyświetlać jej zawartość.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("My point is: {:?}", p);
}Makra atrybutów: `#[my_attribute]`
Makra atrybutów przypominają makra derive, ale można je stosować do dowolnych elementów (funkcji, struktur, modułów itd.). Przyjmują jako dane wejściowe element, do którego zostały dołączone, oraz wszelkie argumenty umieszczone w nawiasach.
Typowe zastosowania obejmują routing we frameworkach internetowych (np. #[get("/users")]) oraz konfigurację testów (np. #[test]).
/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn log_calls(attr: TokenStream, item: TokenStream) -> TokenStream {
// Imagine adding println! statements to 'item'
item
}
/* In 'my_app/src/main.rs' */
use my_macros::log_calls;
#[log_calls("entry_point")]
fn my_function() {
println!("Inside my_function");
}
fn main() {
my_function();
}Kiedy używać poszczególnych makr
- Makra funkcyjne: Do tworzenia niestandardowych minijęzyków lub złożonych przekształceń dowolnych bloków kodu.
- Makra derive: Do automatycznego implementowania traitów dla struktur i enumeracji.
- Makra atrybutów: Do modyfikowania kodu lub generowania kodu wokół określonych elementów (funkcji, struktur) na podstawie niestandardowych atrybutów.
Każdy rodzaj służy innemu celowi, rozszerzając składnię i możliwości języka Rust.
Szybkie sprawdzenie: rodzaje makr
Które z poniższych stwierdzeń dotyczących makr proceduralnych są PRAWDZIWE?
Podsumowanie: potęga generowania kodu
Poznali Państwo zaawansowany świat makr proceduralnych!
- Poznaliśmy makra funkcyjne do tworzenia niestandardowej składni.
- Zrozumieliśmy, jak makra
#[derive]automatyzują implementowanie traitów. - Krótko omówiliśmy makra atrybutów do generowania kodu na poziomie elementów.
Makra proceduralne to potężne narzędzie do ograniczania powtarzalnego kodu i rozszerzania możliwości Rust, które pozwala pisać kod generujący kod!
Często zadawane pytania
Czy lekcja „Makra proceduralne: Derive, Function” jest bezpłatna?
Tak — pełny tekst „Makra proceduralne: Derive, Function” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Learn Rust Coding, przejdź na CoddyKit PRO. Kurs Learn Rust Coding zawiera 3 lekcji w sumie.
Co nauczysz się w „Makra proceduralne: Derive, Function”?
Poznają Państwo makra proceduralne, w tym własne makra `#[derive]` i makra funkcyjne, służące do bardziej złożonego generowania kodu. Ćwiczysz Learn Rust Coding z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Learn Rust Coding?
Nie wymagamy żadnego doświadczenia. Learn Rust Coding w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 3.
Ile czasu zajmuje lekcja „Makra proceduralne: Derive, Function”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Learn Rust Coding?
Tak. Każda lekcja Learn Rust Coding zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Makra deklaratywne (`macro_rules!`)
- Makra proceduralne: Derive, Function
- Praca z Rust w trybie unsafe