0Pricing
Learn Rust Coding · Урок

Процедурные макросы: 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 — локальная установка не требуется.

Все уроки этого курса

  1. Декларативные макросы (`macro_rules!`)
  2. Процедурные макросы: Derive, Function
  3. Взаимодействие с небезопасным Rust
← Назад к Learn Rust Coding