0


[Rust笔记] 规则宏的“卫生保健”

规则宏代码的“卫生保健”

规则宏

mbe

即是由

macro_rules!

宏所定义的宏。它的英文全称是

Macro By Example

。相比近乎“徒手攀岩”的

Cpp

模板·元编程,

rustc

提供了有限的编译时宏代码检查功能(名曰:

Mixed Hygiene

宏的混合保健)。因为

rust

宏代码·被展开于·编译过程中的语法分析阶段(请见下图),所以

rustc

相较于

g++/gcc

拥有更多可用作“代码静态分析”的信息。

b6b3fbfad445448c6ee61bd5e501bfcd.png

宏代码验证功能的有限性体现在

rustc

仅只对·宏展开式·内的

  • 本地变量
  • 标签
  • 当前包引用

执行编译时检查。

咦!“宏展开式”是什么概念?这是一个好问题。在我们开始更深入的讨论之前,有必要先对几个名词解释达成一致的理解。

名词解释

抛开生涩的文字描述,一张附有丰富批注的代码截图被用来形象化如下七个术语词条:

  • 宏规则Rule
  • 匹配模式Syntax Rule
  • 元变量Meta-variable / 捕获Capture- 元变量的概念更宽泛,因为它还包括了rustc预置占位符。比如,$crate。- 而【捕获】仅指·宏规则·的“形参”。
  • 捕获类型Fragment Specifier
  • 宏展开式Transcriber
  • 宏调用
  • 宏展开代码Expansion

请大家来看图,一图抵千词,行文不啰嗦。

8af14f3a2ca540cf0f3940757d14fd67.png

接着,我们再逐一论述【宏的混合保健】是如何保护【本地变量】与【当前包引用】的。

宏保健之本地变量

它解决的是在

  • 宏展开式内定义的“土著”变量local variable

  • 由元变量传入宏的“外来”变量alien variable

之间的命名冲突的问题。简单地讲,

rustc

给·宏规则·内所有元变量限定了一个额外且独立的语法上下文

syntax context

,进而使“外来”变量与“土著”变量相区分。于是,在同一个宏规则内并存两套语法上下文:

  • 宏展开式·语法上下文 —— 限定“土著”变量
  • 元变量·语法上下文 —— 限定“外来”变量

举个例子,请仔细品味!

e211a8373526c69689df998391ca667d.png

上例中

using_a!

宏的输出结果是

8

,而不是

5

,更不是

43

。这涉及了以下几个知识点:

  • 元变量语法上下文·与·宏展开式语法上下文·不互通。- 具体于上例,宏展开代码的第二条变量绑定语句let a = 22;并不能遮蔽其上一条语句let a = 42;对变量a的赋值结果。因此,最后参与表达式(a + 10) / six求值的变量a的值还是42
  • 宏展开式语法上下文·与·宏调用语句语法上下文·相融合,当且仅当它们共处于同一作用域时。若宏被跨模块(甚至跨包) 调用,那么这条原则就不成立了 — 文章的后半程会专门讲到这类场景。具体于上例,- 在宏定义前绑定的变量six能够参与宏展开式(a + 10) / six表达式的求值运算。- 而,在宏定义后绑定的变量four就不能参与宏展开式表达式的计算。- 注意 + 强调:外部绑定变量是否可被用于宏·是取决于“宏定义”的位置,而不是“宏调用”的位置。即,变量绑定既得出现于宏定义之前,它还得与宏(定义 + 调用)同在一个作用域内。这和脚本编程语言(比如,javascript)的惯例有所不同。
  • 在宏展开代码里,由元变量$e代换入的表达式a + 10有着更高的执行优先级。具体于上例,- 请注意表达式a + 10两侧的圆括号。这是因为a + 10整体·作为一个AST表达式结点·被注入宏展开代码,而不是被当作三个没有任何语义与关联的token。后者是Cpp模板元编程的作法,因为Cpp模板是在编译过程中的词法分析阶段被展开。

综上所述,在宏展开代码里,被代入值的表达式是

(42 + 10) / 6 = 8

,而不是

(22 + 10) / 6 = 5

,更不是

42 + 10 / 6 = 43

。将所有分析标入代码,则有

f6b599cc387fd6c8b2ac646b1137aadb.png

若还是感觉有些一知半解,你可尝试注释掉宏展开式内的

let a = 42;

语句。然后,观察程序的编译结果:

0531e58b443fa7c102705004230514a7.png

rustc

的抱怨清晰表达了:“只要语法上下文不一致,即便同名变量

let a = 22;

就糊在眼前,它也视若无睹”。

讨论到此处,我们收获了第一个重要结论是:

在宏展开式内,代表同一个变量的多个【识别符】

identifier

必须

  • 既要,具备完全一样的“词法”名称,
  • 还要,共处于同一个“语法”上下文中,

而不论这些识别符是源于宏内定义的“土著”,还是经由元变量代换而入的“外来者”。

嵌套的语法上下文

故事仍不能结束,因为实际情况还会更复杂一点点儿。简单地讲,元变量语法上下文·还能嵌套包含·宏调用语句语法上下文。即,在宏调用语句中,元变量“实参”包含了·在该语句绑定的变量。

预感文字描述力的不足(哎!汗),我对之前代码稍做修改,举出一个新例子。在新例子中,由元变量

$e

代换入宏展开代码的表达式

a + eight + 10

包含了在·宏调用语句语法上下文·里绑定的变量

eight

rustc

并没有报怨“找不到

eight

的定义”,而是

  1. 先在·元变量语法上下文·内寻找变量eight的定义
  2. 发现没有,再到·宏展开式语法上下文·内寻找
  3. 还是没有,再去·宏调用语句语法上下文·内寻找
  4. 最后,找到let eight = 8;绑定语句。其位于宏定义之后与宏调用之前。

将所有分析标入代码,则有

e0bcb27f6fc0405b828c2d9bef03e7e4.png

至此,关于“本地变量”的故事算是结束了。

宏保健之当前包引用

宏展开代码·默认是从·宏调用语句语法上下文·寻找被使用到的(宏)外部项

item

。因此,一旦某个宏被跨模块(甚至跨包)调用,就会发生

  • 要么,rustc编译失败和报怨:“从当前作用域,找不到被引用的项”。如下例
  • daa22876077225cace8634ff4f08027c.png
  • 要么,虽然没有编译错误,但从·宏调用语句上下文·引入同名却不匹配的项。如下例
  • 49a54c32e28ea5800376eb04d107a5fc.png
rust

保留关键字

crate::

仅指向·程序执行上下文·所在包的根模块,而不是·宏定义上下文·所在包的根模块。就上例而言,即便在上游

crate A

helper!

宏定义内使用完全限定路径

crate::logger::log2db

来引用宏外部函数,下游

crate B

依旧不可避免地出现

  • 要么,找不到B::logger::log2db
  • 要么,找到不正确的B::logger::log2db

的情况,因为

crate::

始终都是指向是

crate B

的根模块,但程序设计意图却是调用

A::logger::log2db

函数。

Mixed Hygiene

要求 @开发者,在宏展开式内,始终以元变量

$crate::

引用当前包。相对于保留关键字

crate::

,元变量

$crate::

总是被展开为宏定义端包根模块的引用路径。具体于上例,在

helper!

宏调用语句被展开之后,

$crate::logger::log2db

会被替换为

A::logger::log2db

。于是,下游程序包

B

就能显示地向上游包

A

寻找依赖项

logger::log2db

函数。

讨论到此处,我们收获了第二个重要结论是:

就宏而言,

  • crate::总是引用宏调用端包的根模块
  • $crate::总是引用宏定义端包的根模块

综上所述,能够正确导出宏的上游

crate A

应该看起来像这样:

#![crate_type = "lib"]
#![crate_name = "A"]
// 导出宏
#[macro_export]
macro_rules! helper {
    ($text: expr) => ($crate::logger::log2db($text))
}
/// 宏展开式的外部项
pub mod logger {
    pub fn log2db(text: String) {
        println!("写 {} 进入数据库", text);
    }
}
/// 单元测试
mod tests {
    #[test]
    fn log2db(){
        helper!("1122".to_string());
    }
}

结束语

虽然文章罗里吧嗦地多次提到“***上下文”显得有些乱,但汇总起来仅有如下三个上下文和解决两类问题

c3bf7a8936a48cb0e53e80e93cf1d079.png

春节假期,我得空系统地精读Rust宏小书(第二版)。相对于两年前对第一版的理解,我这次领悟到的内容更加自恰了,甚至还给我一点儿豁然开朗的感觉。哈哈哈!于是,萌发冲动,想把其中,既让我兴奋,我还有能力讲明白的那部分体会写出来与大家分享。请路过的神仙哥哥与仙女妹妹们阅读指正呀!

rust

太难学,求与君共同进步。


本文转载自: https://blog.csdn.net/u012067469/article/details/128768665
版权归原作者 Rust语言中文社区 所有, 如有侵权,请联系我们删除。

“[Rust笔记] 规则宏的“卫生保健””的评论:

还没有评论