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
每月下载量 2,803
用于 87 个crate(5个直接使用)
13KB
88 行
在Rust 1.15上模拟功能过程宏的自定义 derive
这个crate允许通过自定义 derive(即宏1.1)和 macro_rules! 来创建类似函数的宏(以 foo!(...)
的方式调用),具有过程组件。
这种复杂的机制使得即使功能过程宏(即宏2.0)尚未提供,这些宏也能在稳定的Rust 1.15上运行。
定义这种宏的库需要两个crate:一个是“普通”的,另一个是 proc-macro
。在下面的例子中,我们将它们分别称为 libfoo
和 libfoo-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)
}
}
}
让我们逐行查看编号的行
libfoo
依赖于libfoo-macros
,并导入其宏。libfoo-macros
导出的所有内容(即一个自定义的derive
)都被重新导出到libfoo
的用户。他们不需要直接使用它,但是foo_stringify
宏的展开需要它。- 这个宏调用定义了另一个宏,名为
libfoo__invoke_proc_macro
,它也被导出。这种间接引用是必要的,因为目前重新导出macro_rules!
宏不起作用,并且它再次被foo_stringify
的展开所使用。同样,我们使用一个长的前缀来避免名称冲突。 - 最后,我们定义了我们真正想要的宏。这个宏有一个用户将使用的名称。
- 这个宏的展开将定义一些项目,这些项目的名称在
macro_rules
中不卫生。所以我们把所有东西都包裹在一个额外的{…}
块中,以防止这些名称泄漏。 - 在这里,我们使用在第 (3) 点中定义的宏,这允许我们编写看起来像是调用功能过程宏的东西,但实际上使用自定义的
derive
。这将定义一个名为ProceduralMasqueradeDummyType
的类型,作为使用derive
的占位符。如果libfoo__invoke_proc_macro!
需要多次使用,则每次使用都需要嵌套在另一个块中,以便多个占位符类型的名称不会冲突。 - 除了占位符类型之外,我们的过程组件返回的项目也插入在这里。(在这种情况下是
STRINGIFIED
常量。) - 最后,我们编写了希望宏评估到的表达式。这个表达式可以使用
foo_stringify
输入的部分,它可以包含控制流语句,如return
或continue
,当然也可以引用过程定义的项目。
这个宏可以在表达式上下文中使用。它展开为一个包含一些项目(作为实现细节)并结束于另一个表达式的块表达式。
对于用户
使用 libfoo
的用户不需要担心这些实现细节。他们可以使用 foo_stringify
宏,就像它是一个简单的 macro_rules
宏一样。
#[macro_use] extern crate libfoo;
fn main() {
do_something(foo_stringify!(1 + 2));
}
fn do_something(_: &str) { /* ... */ }
更多
要查看一个更复杂的示例,请参阅 cssparser
的 src/macros.rs
和 cssparser-macros
的 macros/lib.rs
。