#macro-derive #proc-macro #macro-rules

procedural-masquerade

为proc_macro_derive提供伪装成proc_macro的宏_rules

8个版本

使用旧的Rust 2015

0.1.7 2020年6月20日
0.1.6 2018年6月14日
0.1.5 2017年11月11日
0.1.3 2017年10月17日
0.1.1 2017年2月28日

Rust模式 中排名第 1174

Download history 797/week @ 2024-03-13 1026/week @ 2024-03-20 1168/week @ 2024-03-27 1095/week @ 2024-04-03 795/week @ 2024-04-10 888/week @ 2024-04-17 870/week @ 2024-04-24 789/week @ 2024-05-01 786/week @ 2024-05-08 754/week @ 2024-05-15 811/week @ 2024-05-22 895/week @ 2024-05-29 703/week @ 2024-06-05 575/week @ 2024-06-12 754/week @ 2024-06-19 628/week @ 2024-06-26

每月下载量 2,803
用于 87 个crate(5个直接使用)

MIT/Apache

13KB
88

在Rust 1.15上模拟功能过程宏的自定义 derive

这个crate允许通过自定义 derive(即宏1.1)和 macro_rules! 来创建类似函数的宏(以 foo!(...) 的方式调用),具有过程组件。

这种复杂的机制使得即使功能过程宏(即宏2.0)尚未提供,这些宏也能在稳定的Rust 1.15上运行。

定义这种宏的库需要两个crate:一个是“普通”的,另一个是 proc-macro。在下面的例子中,我们将它们分别称为 libfoolibfoo-macros

致谢

使这个crate能够工作的技巧基于 David Tolnay 的想法。非常感谢!

示例

作为一个简单的示例,我们将重新实现 stringify! 宏。由于标准库中已经存在 stringify! 宏,这没有实际用途,而且因为这个crate内部使用了 stringify!,所以这有点荒谬。

尽管如此,它作为简单示例来说明这个crate的使用是有用的。

proc-macro crate

典型的宏1.1的 Cargo.toml 文件

[package]
name = "libfoo-macros"
version = "1.0.0"

[lib]
proc-macro = true

在代码中,我们在一个函数中定义了宏的过程部分。这个函数不会直接由最终用户使用,但仍需要将其重新导出到他们那里(因为 macro_rules! 的限制)。

为了避免名称冲突,我们在函数名称中包含了一个长而明确的前缀。

该函数接收一个包含任意Rust令牌的字符串,并返回一个解析为项目的字符串。返回的字符串可以包含常量、静态项、函数等,但不能直接包含表达式。

#[macro_use] extern crate procedural_masquerade;
extern crate proc_macro;

define_proc_macros! {
    #[allow(non_snake_case)]
    pub fn foo_internal__stringify_const(input: &str) -> String {
        format!("const STRINGIFIED: &'static str = {:?};", input)
    }
}

一个不那么简单的宏可能会使用syn crate来解析其输入,并使用quote crate来生成其输出。

库crate

[package]
name = "libfoo"
version = "1.0.0"

[dependencies]
libfoo-macros = {path = "./macros", version = "1.0"}
#[macro_use] extern crate libfoo_macros;  // (1)

pub use libfoo_macros::*;  // (2)

define_invoke_proc_macro!(libfoo__invoke_proc_macro);  // (3)

#[macro_export]
macro_rules! foo_stringify {  // (4)
    ( $( $tts: tt ) ) => {
        {  // (5)
            libfoo__invoke_proc_macro! {  // (6)
                foo_internal__stringify_const!( $( $tts ) )  // (7)
            }
            STRINGIFIED  // (8)
        }
    }
}

让我们逐行查看编号的行

  1. libfoo 依赖于 libfoo-macros,并导入其宏。
  2. libfoo-macros 导出的所有内容(即一个自定义的 derive)都被重新导出到 libfoo 的用户。他们不需要直接使用它,但是 foo_stringify 宏的展开需要它。
  3. 这个宏调用定义了另一个宏,名为 libfoo__invoke_proc_macro,它也被导出。这种间接引用是必要的,因为目前重新导出 macro_rules! 宏不起作用,并且它再次被 foo_stringify 的展开所使用。同样,我们使用一个长的前缀来避免名称冲突。
  4. 最后,我们定义了我们真正想要的宏。这个宏有一个用户将使用的名称。
  5. 这个宏的展开将定义一些项目,这些项目的名称在 macro_rules 中不卫生。所以我们把所有东西都包裹在一个额外的 {} 块中,以防止这些名称泄漏。
  6. 在这里,我们使用在第 (3) 点中定义的宏,这允许我们编写看起来像是调用功能过程宏的东西,但实际上使用自定义的 derive。这将定义一个名为 ProceduralMasqueradeDummyType 的类型,作为使用 derive 的占位符。如果 libfoo__invoke_proc_macro! 需要多次使用,则每次使用都需要嵌套在另一个块中,以便多个占位符类型的名称不会冲突。
  7. 除了占位符类型之外,我们的过程组件返回的项目也插入在这里。(在这种情况下是 STRINGIFIED 常量。)
  8. 最后,我们编写了希望宏评估到的表达式。这个表达式可以使用 foo_stringify 输入的部分,它可以包含控制流语句,如 returncontinue,当然也可以引用过程定义的项目。

这个宏可以在表达式上下文中使用。它展开为一个包含一些项目(作为实现细节)并结束于另一个表达式的块表达式。

对于用户

使用 libfoo 的用户不需要担心这些实现细节。他们可以使用 foo_stringify 宏,就像它是一个简单的 macro_rules 宏一样。

#[macro_use] extern crate libfoo;

fn main() {
    do_something(foo_stringify!(1 + 2));
}

fn do_something(_: &str) { /* ... */ }

更多

要查看一个更复杂的示例,请参阅 cssparsersrc/macros.rscssparser-macrosmacros/lib.rs

无运行时依赖