如何创建类似函数的过程宏?

How do I create a function-like procedural macro?

a_proc_macro 应该如何定义才能 "returns" 5?

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

阅读 The Rust Programming Language's chapter on macros 说:

Function-like macros define macros that look like function calls. Similarly to macro_rules! macros, they’re more flexible than functions; for example, they can take an unknown number of arguments. However, macro_rules! macros can be defined only using the match-like syntax we discussed in the section “Declarative Macros with macro_rules! for General Metaprogramming” earlier. Function-like macros take a TokenStream parameter and their definition manipulates that TokenStream using Rust code as the other two types of procedural macros do. An example of a function-like macro is an sql! macro that might be called like so:

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

This macro would parse the SQL statement inside it and check that it’s syntactically correct, which is much more complex processing than a macro_rules! macro can do. The sql! macro would be defined like this:

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

This definition is similar to the custom derive macro’s signature: we receive the tokens that are inside the parentheses and return the code we wanted to generate.


Rust 1.45 起,您可以将类似函数的过程宏作为表达式调用。

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

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

example-macro/Cargo.toml

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

[lib]
proc-macro = true

example-macro/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()
    }}
}

这是一个 hack,而且是一个麻烦的 hack,但是 proc-macro-hack crate 可以解决这个问题,并且可以更轻松地使用上述技术生成过程宏。在 proc-macro-hack crate 的帮助下,您可以 运行 来自 Shepmaster 在 stable 上的回答的几乎不变的代码:

  • 编辑两个 Cargo.toml 文件并将 proc-macro-hack = "0.5.11" 添加到依赖项部分;
  • src/main.rs 中添加 #[proc_macro_hack] use example_macro::a_proc_macro;,并从本地命名空间调用 a_proc_macro!
  • example-macro/src/lib.rs中的a_proc_macro定义前添加#[proc_macro_hack::proc_macro_hack]