Процедурные макросы: Derive, Function
Изучите процедурные макросы, включая пользовательские макросы `#[derive]` и макросы, подобные функциям, для более сложной генерации кода.
«Процедурные макросы: Derive, Function» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 2 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 3 уроков всего.
Введение в процедурные макросы
Добро пожаловать в мир процедурных макросов! В отличие от декларативных макросов (macro_rules!), процедурные макросы похожи на функции, работающие непосредственно с кодом Rust.
Они принимают TokenStream в качестве входных данных, обрабатывают его и возвращают другой TokenStream. Это позволяет выполнять значительно более сложную генерацию кода.
Типы процедурных макросов
Rust предлагает три основных типа процедурных макросов:
- Макросы, подобные функциям: Они выглядят и работают как обычные вызовы функций, например
my_macro!(...). - Производные макросы: Они позволяют автоматически реализовывать трейты для структур и перечислений с помощью
#[derive(MyTrait)]. - Макросы-атрибуты: Они позволяют определять пользовательские атрибуты, которые можно применять к элементам, например
#[route("/path")].
Крейт `proc-macro`
Процедурные макросы должны находиться в собственном специальном типе крейта. В файле Cargo.toml это объявляется следующим образом:
Так Вы сообщаете Cargo, что крейт содержит макросы, преобразующие код во время компиляции.
[package]
name = "my_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = trueОбъяснение макросов, подобных функциям
Макросы, подобные функциям, определяются с помощью атрибута #[proc_macro], применённого к функции, которая принимает proc_macro::TokenStream и возвращает такой же объект.
Они полезны для пользовательских предметно-ориентированных языков или сложного повторения кода, с которыми macro_rules! не справляется.
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
}Пример макроса, подобного функции
Вот концептуальный пример. Макрос `greet_name!`, который генерирует инструкцию вывода. Фактическая реализация должна разобрать входной TokenStream, чтобы извлечь имя.
Хотя определение макроса сложное, его использование простое и мощное:
/* 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");
}Объяснение макросов `#[derive]`
Производные макросы являются наиболее распространённым типом. Они позволяют автоматически реализовывать трейт для структуры или перечисления.
Когда Вы пишете #[derive(Debug)], во время компиляции запускается макрос, который генерирует необходимый код для реализации Вашим типом трейта Debug.
Реализация `#[derive]` (концепция)
Пользовательские производные макросы обычно создаются с использованием библиотек, таких как syn (для разбора кода Rust в AST) и quote (для генерации кода Rust из AST).
Вы определяете функцию с аннотацией #[proc_macro_derive(MyTrait)], которая принимает TokenStream, представляющий структуру или перечисление, и возвращает сгенерированную реализацию трейта.
/* 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()
}Пример использования `#[derive(Debug)]`
Рассмотрим практический пример производного макроса, которым Вы, вероятно, уже пользовались: Debug. Добавив #[derive(Debug)] к нашей структуре Point, Rust автоматически реализует трейт Debug, позволяя удобно выводить её содержимое.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("My point is: {:?}", p);
}Макросы-атрибуты: `#[my_attribute]`
Макросы-атрибуты похожи на производные макросы, но могут применяться к любому элементу (функциям, структурам, модулям и т. д.). Они принимают в качестве входных данных элемент, к которому прикреплены, а также любые аргументы в скобках.
Обычно они используются для маршрутизации в веб-фреймворках (например, #[get("/users")]) или настройки тестов (например, #[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();
}Когда использовать каждый тип макроса
- Макросы, подобные функциям: Для пользовательских мини-языков или сложных преобразований произвольных блоков кода.
- Производные макросы: Для автоматической реализации трейтов в структурах и перечислениях.
- Макросы-атрибуты: Для изменения или генерации кода вокруг определённых элементов (функций, структур) на основе пользовательских атрибутов.
Каждый тип служит своей цели, расширяя синтаксис и возможности Rust.
Быстрая проверка: типы макросов
Какие из следующих утверждений о процедурных макросах являются TRUE?
Повторение: возможности генерации кода
Вы изучили продвинутый мир процедурных макросов!
- Мы рассмотрели макросы, подобные функциям, для пользовательского синтаксиса.
- Вы узнали, как макросы
#[derive]автоматизируют реализации трейтов. - Мы кратко рассмотрели макросы-атрибуты для генерации кода на уровне элементов.
Процедурные макросы — мощный инструмент для сокращения шаблонного кода и расширения возможностей Rust, позволяющий Вам писать код, который пишет код!
Часто задаваемые вопросы
Урок «Процедурные макросы: Derive, Function» бесплатный?
Да — полный текст урока «Процедурные макросы: Derive, Function» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 3 уроков всего.
Чему я научусь в уроке «Процедурные макросы: Derive, Function»?
Изучите процедурные макросы, включая пользовательские макросы `#[derive]` и макросы, подобные функциям, для более сложной генерации кода. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 3.
Сколько времени занимает урок «Процедурные макросы: Derive, Function»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Декларативные макросы (`macro_rules!`)
- Процедурные макросы: Derive, Function
- Взаимодействие с небезопасным Rust