02-过程宏
8 分钟阅读
译文 · 基于 The Rust Reference
原文链接: https://doc.rust-lang.org/reference/procedural-macros.html
r[macro.proc]
过程宏
r[macro.proc.intro] 过程宏 允许以执行函数的方式创建语法扩展。过程宏有三种风格:
过程宏允许你在编译期运行对 Rust 语法进行操作的代码,既消费也产生 Rust 语法。你可以大致把过程宏想成从一份 AST 到另一份 AST 的函数。
r[macro.proc.def]
过程宏必须定义在 crate 类型 为 proc-macro 的 crate 的根中。这些宏不得从定义它们的 crate 中使用,只能在另一个 crate 中导入后使用。
注意 使用 Cargo 时,过程宏 crate 通过清单中的
proc-macro键定义:
1 2[lib] proc-macro = true
r[macro.proc.result] 作为函数,它们必须要么返回语法,要么 panic,要么无限循环。返回的语法根据过程宏的种类替换或添加语法。panic 会被编译器捕获并变成编译错误。无限循环不会被编译器捕获,会使编译器挂起。
过程宏在编译期间运行,因此拥有与编译器相同的资源。例如,标准输入、错误和输出与编译器所能访问的相同。类似地,文件访问也相同。因此,过程宏具有与 Cargo 的构建脚本 相同的安全顾虑。
r[macro.proc.error]
过程宏有两种报告错误的方式。第一种是 panic。第二种是发出 [compile_error] 宏调用。
r[macro.proc.proc_macro-crate]
proc_macro crate
r[macro.proc.proc_macro-crate.intro]
过程宏 crate 几乎总会链接到编译器提供的 proc_macro crate。proc_macro crate 提供编写过程宏所需的类型,以及使其更容易的设施。
r[macro.proc.proc_macro-crate.token-stream]
该 crate 主要包含 TokenStream 类型。过程宏在 token 流 而不是 AST 节点上操作,这对编译器和过程宏来说都是随时间远为稳定的接口。token 流 大致等价于 Vec<TokenTree>,其中 TokenTree 大致可看作词法 token。例如 foo 是 Ident token,. 是 Punct token,1.2 是 Literal token。与 Vec<TokenTree> 不同,TokenStream 类型克隆起来很便宜。
r[macro.proc.proc_macro-crate.span]
所有 token 都有关联的 Span。Span 是不透明的值,不能修改但可以制造。Span 表示程序中一段源代码的范围,主要用于错误报告。虽然你不能修改 Span 本身,但你始终可以更改与任何 token 关联 的 Span,例如通过从另一个 token 获取 Span。
r[macro.proc.hygiene]
过程宏卫生性
过程宏是 非卫生的。这意味着它们的行为就好像输出 token 流只是内联写在它旁边的代码中一样。这意味着它受外部项影响,也会影响外部导入。
宏作者需要小心,以确保在此限制下他们的宏能在尽可能多的上下文中工作。这通常包括使用指向库中项的绝对路径(例如,用 ::std::option::Option 而不是 Option),或确保生成的函数具有不太可能与其他函数冲突的名称(如用 __internal_foo 而不是 foo)。
r[macro.proc.proc_macro]
proc_macro 属性
r[macro.proc.proc_macro.intro]
proc_macro 属性 定义 [函数式][macro.invocation] 过程宏。
此宏定义忽略其输入,并向其作用域发出函数 answer。
| |
我们可以在二进制 crate 中使用它,向标准输出打印 “42”。
| |
r[macro.proc.proc_macro.syntax]
proc_macro 属性使用 [MetaWord] 语法。
r[macro.proc.proc_macro.allowed-positions]
proc_macro 属性只能应用于类型为 fn(TokenStream) -> TokenStream 的 pub 函数,其中 TokenStream 来自 proc_macro crate。它必须具有 [“Rust” ABI][items.fn.extern]。不允许其他函数限定符。它必须位于 crate 的根中。
r[macro.proc.proc_macro.duplicates]
proc_macro 属性在一个函数上只能指定一次。
r[macro.proc.proc_macro.namespace]
proc_macro 属性在 crate 根的 宏命名空间 中以与函数相同的名称公开定义该宏。
r[macro.proc.proc_macro.behavior]
函数式过程宏的函数式宏调用会把宏调用定界符内的内容作为输入 TokenStream 参数传入,并用该函数的输出 TokenStream 替换整个宏调用。
r[macro.proc.proc_macro.invocation] 函数式过程宏可以在任何宏调用位置被调用,包括:
r[macro.proc.derive]
proc_macro_derive 属性
r[macro.proc.derive.intro]
将 proc_macro_derive 属性 应用于函数会定义一个 derive 宏,可由 derive 属性 调用。这些宏被给予 结构体、枚举 或 联合体 定义的 token 流,并可在其后发出新的 项。它们也可以声明并使用 derive 宏辅助属性。
此 derive 宏忽略其输入,并追加定义一个函数的 token。
| |
要使用它,我们可能会写:
| |
r[macro.proc.derive.syntax]
proc_macro_derive 属性的语法为:
@root ProcMacroDeriveAttribute ->
`proc_macro_derive` `(` DeriveMacroName ( `,` DeriveMacroAttributes )? `,`? `)`
DeriveMacroName -> IDENTIFIER
DeriveMacroAttributes ->
`attributes` `(` ( IDENTIFIER (`,` IDENTIFIER)* `,`?)? `)`
derive 宏的名称由 [DeriveMacroName] 给出。可选的 attributes 参数在 [macro.proc.derive.attributes] 中描述。
r[macro.proc.derive.allowed-positions]
proc_macro_derive 属性只能应用于定义在 crate 根中、具有 [Rust ABI][items.fn.extern]、类型为 fn(TokenStream) -> TokenStream 的 pub 函数,其中 TokenStream 来自 proc_macro crate。该函数可以是 const,并可以使用 extern 显式指定 Rust ABI,但不得使用任何其他 [限定符][FunctionQualifiers](例如,它不得是 async 或 unsafe)。
r[macro.proc.derive.duplicates]
proc_macro_derive 属性在一个函数上只能使用一次。
r[macro.proc.derive.namespace]
proc_macro_derive 属性在 crate 根的 宏命名空间 中公开定义该 derive 宏。
r[macro.proc.derive.output]
输入 TokenStream 是应用了 derive 属性的项的 token 流。输出 TokenStream 必须是(可能为空的)一组项。这些项被追加在输入项之后,位于同一 模块 或 块 内。
r[macro.proc.derive.attributes]
Derive 宏辅助属性
r[macro.proc.derive.attributes.intro] Derive 宏可以声明 derive 宏辅助属性,以在应用该 derive 宏的 项 的作用域内使用。这些 属性 是 惰性的。虽然它们的目的是供声明它们的宏使用,但任何宏都可以看到它们。
r[macro.proc.derive.attributes.decl]
通过将其标识符加入 proc_macro_derive 属性的 attributes 列表来声明 derive 宏的辅助属性。
这声明一个辅助属性,然后忽略它。
| |
要使用它,我们可能会写:
| |
r[macro.proc.derive.attributes.scope] 当 derive 宏调用应用于一项时,该 derive 宏引入的辅助属性进入作用域:1) 用于应用于该项、且在词法上位于该 derive 宏调用之后的属性;以及 2) 用于应用于该项内部字段和变体的属性。
注意 rustc 目前允许在引入它们的宏之前使用 derive 辅助属性。这种乱序使用的 derive 辅助属性可能不会遮蔽其他属性宏。此行为已弃用,计划移除。
1 2 3 4 5#[helper] // 已弃用,将来会变成硬错误。 #[derive(WithHelperAttr)] struct Struct { field: (), }更多细节见 Rust issue #79202。
r[macro.proc.attribute]
proc_macro_attribute 属性
r[macro.proc.attribute.intro]
proc_macro_attribute 属性 定义可用作 外部属性 的 属性宏。
此属性宏获取输入流并原样发出,实际上是一个空操作属性。
| |
这在编译器输出中显示属性宏所看到的字符串化 TokenStream。
| |
| |
r[macro.proc.attribute.syntax]
proc_macro_attribute 属性使用 [MetaWord] 语法。
r[macro.proc.attribute.allowed-positions]
proc_macro_attribute 属性只能应用于类型为 fn(TokenStream, TokenStream) -> TokenStream 的 pub 函数,其中 TokenStream 来自 proc_macro crate。它必须具有 [“Rust” ABI][items.fn.extern]。不允许其他函数限定符。它必须位于 crate 的根中。
r[macro.proc.attribute.duplicates]
proc_macro_attribute 属性在一个函数上只能指定一次。
r[macro.proc.attribute.namespace]
proc_macro_attribute 属性在 crate 根的 宏命名空间 中以与函数相同的名称定义该属性。
r[macro.proc.attribute.use-positions] 属性宏只能用在:
r[macro.proc.attribute.inner] 属性宏不能用作 内部属性。
r[macro.proc.attribute.outline-mod] 对于宏输入中存在的任何 外置模块,只传入模块声明的 token;模块的文件内容不会被加载或包含在输入中。
r[macro.proc.attribute.behavior]
第一个 TokenStream 参数是属性名之后的定界 token 树,但不包括外层定界符。若所应用的属性只包含属性名,或属性名后跟空定界符,则 TokenStream 为空。
第二个 TokenStream 是该项的其余部分,包括该项上的其他 属性。
应用该属性的项被返回的 TokenStream 中的零个或多个项替换。
r[macro.proc.token]
声明宏 token 与过程宏 token
r[macro.proc.token.intro]
声明式 macro_rules 宏和过程宏对 token(或者说 TokenTree)使用相似但不同的定义。
r[macro.proc.token.macro_rules]
macro_rules 中的 token 树(对应于 tt 匹配器)定义为
- 定界分组(
(...)、{...}等) - 语言支持的所有运算符,包括单字符和多字符的(
+、+=)。- 注意此集合不包括单引号
'。
- 注意此集合不包括单引号
- 字面量(
"string"、1等)- 注意取负(例如
-1)从不是此类字面量 token 的一部分,而是单独的运算符 token。
- 注意取负(例如
- 标识符,包括关键字(
ident、r#ident、fn) - 生命周期(
'ident) macro_rules中的元变量替换(例如macro_rules! mac { ($my_expr: expr) => { $my_expr } }展开后的$my_expr,无论传入的表达式如何,都将被视为单个 token 树)
r[macro.proc.token.tree] 过程宏中的 token 树定义为
- 定界分组(
(...)、{...}等) - 语言支持的运算符中使用的所有标点字符(
+,但不是+=),以及单引号'字符(通常用于生命周期,生命周期的拆分与连接行为见下文) - 字面量(
"string"、1等)- 取负(例如
-1)作为整数和浮点字面量的一部分受支持。
- 取负(例如
- 标识符,包括关键字(
ident、r#ident、fn)
r[macro.proc.token.conversion.intro] 当 token 流传入和传出过程宏时,会考虑这两种定义之间的不匹配。注意下面的转换可能惰性发生,因此若 token 实际上未被检查,它们可能不会发生。
r[macro.proc.token.conversion.to-proc_macro] 当传入过程宏时
- 所有多字符运算符被拆成单字符。
- 生命周期被拆成
'字符和标识符。 - 关键字元变量
$crate作为单个标识符传入。 - 所有其他元变量替换表示为其底层 token 流。
- 在为保持分析优先级而必要时,此类 token 流可能被包装进带隐式定界符(
Delimiter::None)的定界分组(Group)。 tt和ident替换从不包装进此类分组,始终表示为其底层 token 树。
- 在为保持分析优先级而必要时,此类 token 流可能被包装进带隐式定界符(
r[macro.proc.token.conversion.from-proc_macro] 当从过程宏发出时
- 标点字符在适用时被粘合成多字符运算符。
- 单引号
'与标识符连接时被粘合成生命周期。 - 负数字面量被转换为两个 token(
-和字面量),在为保持分析优先级而必要时可能包装进带隐式定界符(Delimiter::None)的定界分组(Group)。
r[macro.proc.token.doc-comment]
注意声明宏和过程宏都不支持文档注释 token(例如 /// Doc),因此当传入宏时,它们总是被转换为表示其等价 #[doc = r"str"] 属性的 token 流。