Как создать процедурный макрос, похожий на функцию?

Как должен a_proc_macro быть определенным так, чтобы он "возвращал" 5?

fn main() {
    let a = a_proc_macro!();
    assert!(a == 5);
}

2 ответа

Решение

Чтение ржавчины Язык программирования в главе о макросах говорит:

Макросы, подобные функциям, определяют макросы, которые выглядят как вызовы функций. Аналогично macro_rules!макросы, они более гибкие, чем функции; например, они могут принимать неизвестное количество аргументов. Однако,macro_rules!макросы могут быть определены только с использованием синтаксиса, аналогичного синтаксису, который мы обсуждали в разделе "Декларативные макросы сmacro_rules!для общего метапрограммирования " ранее. Функциональные макросы требуютTokenStream параметр и их определение управляет этим TokenStreamиспользуя код Rust, как и другие два типа процедурных макросов. Примером макроса, подобного функции, являетсяsql! макрос, который можно назвать так:

let sql = sql!(SELECT * FROM posts WHERE id=1);

Этот макрос будет анализировать внутри него инструкцию SQL и проверять ее синтаксическую правильность, что является гораздо более сложной обработкой, чем macro_rules!макрос может сделать. Вsql! макрос будет определяться так:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

Это определение похоже на подпись макроса настраиваемого извлечения: мы получаем токены, заключенные в круглые скобки, и возвращаем код, который хотели сгенерировать.


example
├── Cargo.toml
├── example-macro
│   ├── Cargo.toml
│   ├── src
│   │   └── lib.rs
├── src
│   └── main.rs

Cargo.toml

[package]
name = "example"
version = "0.1.0"
edition = "2018"

[dependencies]
example-macro = { path = "example-macro" }

src / main.rs

#![feature(proc_macro_hygiene)]

fn main() {
    assert_eq!(example_macro::a_proc_macro!(), 5);
}

Начиная с Rust 1.39, вы не можете вызывать функциональный процедурный макрос нигде, кроме как на верхнем уровне модуля (который включает корневой модуль ящика). Вам нужно будет использовать nightly Rust, если вы хотите использовать его как выражение.

пример-макрос /Cargo.toml

[package]
name = "example-macro"
version = "0.1.0"
edition = "2018"

[lib]
proc-macro = true

пример макроса / SRC / lib.rs

extern crate proc_macro;

use proc_macro::TokenStream;

#[proc_macro]
pub fn a_proc_macro(_input: TokenStream) -> TokenStream {
    "5".parse().unwrap()
}

Смотрите также:

Прямое определение процедурных макросов, подобных выражениям, пока что невозможно в стабильной версии Rust. Если вы можете использовать каждую ночь, ответ Шепмастера показывает, как это сделать.

Если вы используете стабильную версию, вы все равно можете эмулировать процедурные макросы, подобные выражениям, следующим образом:

  • определить процедурный макрос, который расширяется до функции, которая вычисляет выражение, которое вы хотите вызвать;
  • затем определите обычный макрос, который расширяется до блока, в который встроены определение и вызов функции.

В вашем случае вы бы определили процедурный макрос следующим образом:

#[proc_macro]
pub fn a_proc_macro_impl(_input: TokenStream) -> TokenStream {
    "fn output() -> usize { 5 }".parse().unwrap()
}

... помощник macro_rules! макрос следует этой схеме:

macro_rules! a_proc_macro {
    ($($t:tt)*) => {{
        struct _X;
        impl _X {
            a_proc_macro!($($t)*);
        }
        _X::output()
    }}
}

Это хакер, и к тому же громоздкий, но на помощь приходит ящик proc-macro-hack, который упрощает создание процедурных макросов с использованием описанной выше техники. С помощьюproc-macro-hack crate, вы можете запустить практически неизмененный код из ответа Шепмастера на стабильной версии:

  • редактировать оба Cargo.toml файлы и добавить proc-macro-hack = "0.5.11" в раздел зависимостей;
  • добавлять #[proc_macro_hack] use example_macro::a_proc_macro; в src/main.rsи вызвать a_proc_macro! из локального пространства имен.
  • добавлять #[proc_macro_hack::proc_macro_hack] до определения a_proc_macro в example-macro/src/lib.rs.
Другие вопросы по тегам