02-过程宏

过程宏 — The Rust Reference

译文 · 基于 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] 过程宏。

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 宏辅助属性。

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 属性 定义可用作 外部属性 的 属性宏。

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 树。

r[macro.proc.token.conversion.from-proc_macro] 当从过程宏发出时

  • 标点字符在适用时被粘合成多字符运算符。
  • 单引号 ' 与标识符连接时被粘合成生命周期。
  • 负数字面量被转换为两个 token(- 和字面量),在为保持分析优先级而必要时可能包装进带隐式定界符(Delimiter::None)的定界分组(Group)。

r[macro.proc.token.doc-comment] 注意声明宏和过程宏都不支持文档注释 token(例如 /// Doc),因此当传入宏时,它们总是被转换为表示其等价 #[doc = r"str"] 属性的 token 流。

最后修改 August 21, 2026: 更新 (76fc81a2e)