宏系统

声明宏

过程宏

类别形式函数名称函数签名
函数式#[paoc_macro]函数名即宏名(TokenStream) -> TokenStream
属性式#[proc_macro_attribute]函数名即宏名(TokenStream, TokenStream) -> TokenStream
derive#[proc_macro_derive(Name)]或者#[proc_macro_derive(Name, attributes(attr))]任意,因为宏名是Name(TokenStream) -> TokenStream

proc-macro crate

定义一个过程宏crate的方式是将在crate type设为proc-macro.

当使用Cargo时, 过程宏crate是将Cargo.toml中的lib.proc-macro设为true值.

[lib]
proc-macro = true

它只能导出过程宏, 正常的函数、类型、模块、macro_rules! 等内容都不能导出, 但可以仅在其内部定义和使用.

函数式过程宏

类型函数的过程宏, 像声明宏那样被调用, 即makro!(...). 它是唯一一个在单独看调用形式时, 无法与声明宏区分开的宏.

函数式过程宏的简单编写框架如下所示:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

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

}

可以看到, 这实际上只是从一个TokenStream到另一个TokenStream的映射, 其中 input 是调用分隔符内的标记.

属性式过程宏

属性式过程宏定义了可添加到条目的的新外部属性.这种宏通过 #[attr]#[attr(…)] 方式调用, 其中 是任意标记树.

属性式过程宏的简单框架如下所示:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn attribute_name_macro(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
    annotated_item
}
}

需要注意的是, 与其它两个过程宏不同, 这种宏有两个输入参数.

  • 第一个参数是属性名称后面的带分隔符的标记树, 不包括它周围的分隔符.如果只有属性名称(其后不带标记树, 比如 #[attr]), 则这个参数的值为空.
  • 第二个参数是添加了该过程宏属性的条目, 但不包括该过程宏所定义的属性.因为这是一个active属性, 在传递给过程宏之前, 该属性将从条目中剥离出来.

derive式过程宏

derive式过程宏为derive属性定义了新的输入.这种宏通过将其名称提供给derive属性的输入来调用, 例如 #[derive(derive_name)].

derive式过程宏的简单框架如下所示:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro_derive(derive_name)]
pub fn derive_name_macro(input: TokenStream) -> TokenStream {
    TokenStream::new()
}
}

proc_macro_derive稍微特殊一些, 因为它需要一个额外的标识符, 此标识符将成为 derive 宏的实际名称. 输入标记流是添加了 derive 属性的条目, 也就是说, 它将始终是 enumstruct 或者 union 类型, 因为这些是 derive 属性仅可以添加上去的条目.

输出的标记流将被追加到带注释的条目所处的块或模块, 所以要求标记流由一组有效条目组成.

属性宏与 derive 宏的显著区别在于, 属性宏生成的标记是完全替换性质, 而 derive 宏生成的标记是追加性质.

derive式过程宏的辅助属性

derive 宏又有一点特殊, 因为它可以添加仅在条目定义范围内可见的附加属性. 这些属性被称为派生宏辅助属性(derive macro helper attributes), 并且是惰性的(inert). 辅助属性的目的是在每个结构体字段或枚举体成员的基础上为 derive 宏提供额外的可定制性.

辅助属性的定义方式是向 proc_macro_derive 属性增加 attributes(helper0, helper1, ..) 参数, 该参数可包含用逗号分隔的标识符列表(即辅助属性的名称).

编写带辅助属性的`derive``宏的简单框架如下所示:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro_derive(derive_name, attributes(helper0, helper1, ...))]
pub fn derive_name_macro(input: TokenStream) -> TokenStream {
    TokenStream::new()
}
}

这就是辅助属性的全部内容.在过程宏中使用(或者说消耗)辅助属性, 得检查字段和成员的属性, 来判断它们是否具有相应的辅助属性 如果条目使用了所有 derive 宏都未定义的辅助属性, 那么会出现错误, 因为编译器会尝试将这个辅助属性解析为普通属性(而且这个属性并不存在).

Reference