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

news2024/11/28 6:36:40

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

规则宏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包含了在·宏调用语句语法上下文·里绑定的变量eightrustc并没有报怨“找不到eight的定义”,而是

  1. 先在·元变量语法上下文·内寻找变量eight的定义

  2. 发现没有,再到·宏展开式语法上下文·内寻找

  3. 还是没有,再去·宏调用语句语法上下文·内寻找

  4. 最后,找到let eight = 8;绑定语句。其位于宏定义之后与宏调用之前。

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

e0bcb27f6fc0405b828c2d9bef03e7e4.png

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

宏保健之当前包引用

宏展开代码·默认是从·宏调用语句语法上下文·寻找被使用到的(宏)外部项item。因此,一旦某个宏被跨模块(甚至跨包)调用,就会发生

  • 要么,rustc编译失败和报怨:“从当前作用域,找不到被引用的项”。如下例

  • daa22876077225cace8634ff4f08027c.png

  • 要么,虽然没有编译错误,但从·宏调用语句上下文·引入同名却不匹配的项。如下例

  • 49a54c32e28ea5800376eb04d107a5fc.png

rust保留关键字crate::仅指向·程序执行上下文·所在包的根模块,而不是·宏定义上下文·所在包的根模块。就上例而言,即便在上游crate Ahelper!宏定义内使用完全限定路径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太难学,求与君共同进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pointcovn 阅读笔记

各种点云采样算法 https://blog.csdn.net/weixin_41485242/article/details/107150963 Inverse Density 1.2 Inverse Density Importance Sampling (IDIS): 这个也比较好理解,简而言之就是根据每个点的密度来对其重新进行排序,尽可能地保留密度比较低的地…

python第九章 异常笔记

和Java类似程序运行有异常的时候,服务器会采用系统默认的异常处理机制:返回信息,终止程序。异常的类型:常见异常类型:1.NameError:访问了未定义的变量2.IndexError:越界访问3.AttributeError&am…

网络原理-网络发展史和通信基础

目录 1.网络发展史 面向终端的计算机网络 计算机网络阶段(局域网LAN) 组网方式 计算机网络互联阶段(广域网WAN) 2.通信基础 IP地址 端口号 网络协议 协议的作用 知名协议的默认端口 协议分层 分层的作用 OSI七层模型 TCP/IP五层模型 网络设备所在分层 网络分层…

《数据结构》八大排序和拓展的排序(详细教学并提供多种版本、动态图分析)

今天,我将带来数据结构的排序算法,排序算法作为校招中常考知识点之一,我们必须要熟练的掌握它,对自己提出高要求,才能有高回报。 目录排序的概念和应用内部排序和外部排序排序算法需要掌握的知识插入排序1.直接插入排序2.希尔排序…

【教程】Python实时检测CPU和GPU的功耗

目录 前言 GPU功耗检测方法 CPU功耗检测方法 sudo的困扰与解决 完整功耗分析示例代码 转载请注明出处:小锋学长生活大爆炸[xfxuezhang.cn] 前言 相关一些检测工具挺多的,比如powertop、powerstat、s-tui等。但如何通过代码的方式来实时检测&#xf…

Unsafe Fileupload-基础篇(文件上传绕过技巧与upload-labs靶场)

数据来源 本文仅用于信息安全的学习,请遵守相关法律法规,严禁用于非法途径。若观众因此作出任何危害网络安全的行为,后果自负,与本人无关。 文件上传基础 01 什么是文件上传 02 文件上传产生漏洞的原因 03 文件上传漏洞危害 0…

济人药业更新招股书:计划在A股上市,中成药业务收入持续下滑

近日,安徽济人药业股份有限公司(下称“济人药业”)递交预披露更新招股书,准备在上海证券交易所主板上市。据贝多财经了解,济人药业于2022年7月1日递交上市申请,此次更新了截至2022年6月30日的财务数据等信息…

Android深入系统完全讲解(42)

红色部分 pc 000007cc 代表当前 pc 指向的位置。libnative-lib.so 代表在哪个库里面。于是我 们就需要知道,libnative-lib.so 库的 pc 000007cc 偏移位置,是个什么代码。 我们从 NDK 开发包中找到 D:\android-ndk-r19c\toolchains\arm-linux-androideabi…

远程控制软件

远程控制软件1. 概述2. TeamViewer3. Todesk4. 向日葵5. AnyDesk6. Splashtop结束语1. 概述 出门在外或者工作时突然需要访问家中的电脑拿取文件或者资料时,是直接跑回家拿去、还是委托家里人员帮忙呢? 这时候你就需要一类软件来完成这个任务了&#xf…

前端图片压缩方案及代码实现

1. 为什么要进行图片压缩? 随着互联网的发展,图片在各种网站和应用中铺天盖地,运营人员在后台管理系统中上传图片时常常忽略的图片的体积大小,随之产生的带宽和服务器容量也大大增加,图片压缩的需求随之产生。 常见的压缩图片的…

【笔记】SemGCN

一. 论文总结 1.1 核心贡献 提出了一种改进的图卷积操作,称为语义图卷积(SemGConv),它源自cnn。其关键思想是学习图中暗示的边的信道权值,然后将它们与核矩阵结合起来。这大大提高了图卷积的能力。其次,我们引入了SemGCN&#x…

GPU服务器上跑深度学习模型

1 问题来源 近期在本地 Windows 系统上跑深度学习人群计数模型时,由于笔记本 NVIDIA 显卡 NVIDIA GeForce GTX 1650 的专用 GPU 内存只有 4 GB,无法设置较大的 batchsize 进行训练,导致模型训练时间过长,且易发生内存溢出&#xf…

年初五,迎财神 | 一张码如何实现多渠道(微信、支付宝、云闪付...)收款

大家好,我是小悟 今天是正月初五,天气超级好,也是迎财神的日子,祝大家顺风顺水,财源滚滚,钱兔似锦。 既然要发财,那自然少不了收款咯。如果你是一个商家,肯定是想收款的方式越方便越…

【手写 Promise 源码】第三篇 - 实现一个简版 Promise

一,前言 上一篇,结合示例介绍了 Promise 相关功能与特性分析,包含以下内容: Promise 基础特性;Promise 实例 API(原型方法);Promise 静态 API(类方法)&…

【数据结构】极致详解:树与二叉树(中)——顺序存储实现

目录 📔前言📔: 📙一、顺序存储结构📙: 📘二、堆📘: 1.堆的概念及结构: 2.堆的性质: 3.堆的实现(本文重点)&#xf…

离线用户召回定时更新系列二

3.6.3 特征处理原则 离散数据 one-hot编码连续数据 归一化图片/文本 文章标签/关键词提取embedding3.6.4 优化训练方式 使用Batch SGD优化 加入正则化防止过拟合 3.6.5 spark LR 进行预估 目的:通过LR模型进行CTR预估步骤: 1、需要通过spark读取HIVE外…

Nacos学习笔记【part1】安装与注册服务

一、概述与安装 Nacos 是是一个构建云原生应用的动态服务发现、配置管理和服务管理平台,用于发现、配置和管理微服务,提供了一组简单易用的特性集,快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 更敏捷和容易地构建…

分享136个ASP源码,总有一款适合您

ASP源码 分享136个ASP源码,总有一款适合您 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下..., 136个ASP源码下载链接:https://pan.baidu.com/s/11db_K2QXns5pm8vMZBVPSw?pwds0lb 提取码&#x…

js 文字转语音 api SpeechSynthesisUtterance

SpeechSynthesisUtterance基本介绍 SpeechSynthesisUtterance是HTML5中新增的API,用于将指定文字合成为对应的语音.也包含一些配置项,指定如何去阅读(语言,音量,音调)等 官方文档地址(https://developer.mozilla.org/zh-CN/docs/Web/API/SpeechSynthesisUtterance…

【JavaEE初阶】第七节.多线程(基础篇)单例模式(案例一)

欢迎大家跟我一起来学习有关多线程的有关内容!!!!!!!!!! 文章目录 前言 一、单例模式的概念 二、单例模式的简单实 2.1 饿汉模式 2.2 懒汉模式 总结 前言…