宏macro 宏在Rust里是指一组相关特性的集合称谓:使用macrorules!构建的声明宏(declarativemacro)3种过程宏自定义〔derive〕宏,用于struct或enum,可以为其指定随derive属性添加的代码类似属性的宏,可以在任意条目上添加自定义属性类似于函数的宏,看起来像函数调用,对其指定为参数的token进行操作 macrorules!中有一些奇怪的地方。在将来,会有第二种采用macro关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,macrorules!实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数Rust程序员使用宏而非编写宏的事实,此处不再深入探讨macrorules!。请查阅在线文档或其他资源,如TheLittleBookofRustMacros(https:danielkeep。github。iotlbormbookindex。html)来更多地了解如何写宏。函数与宏的差别 从本质上看,宏是用来编写可以生成其它代码的代码(元编程,metaprograming)。函数在定义签名时,必须声明参数的个数和类型,而宏可以处理可变的参数。编译器会在解释代码前展开宏。宏的定义比函数复杂得多,难以阅读、理解和维护。在某个文件调用宏时,必须提前定义宏或将宏引入当前作用域。而函数可以在任何位置定义并在任何位置使用。基于属性来生成代码的过程宏 这种形式更像函数(某种形式的过程)一些。它会接收并操作输入的Rust代码,并生成另外一些Rust代码作为结果。 三种过程宏:自定义派生属性宏函数宏 创建过程宏时,宏定义必须单独放在它们自己的包中,并且使用特殊的包类型。例:srclib。〔someattribute〕someattribute是一个使用特定宏的占位符。pubfnsomename(input:TokenStream)TokenStream{TokenStream类型由包含在Rust中的procmacrocrate定义并表示token序列。这是宏的核心:宏所操作的源代码构成了输入TokenStream,宏产生的代码是输出TokenStream。}自定义derive宏(派生宏) 需求:创建一个hellomacrolibrary包,定义一个拥有关联函数hellomacro的HelloMacrotrait我们定义一个能自动实现该trait的过程宏只需要在对应的类型上标准〔derive(HelloMacro)〕,就能得到hellomacro函数的默认实现 首先新建并打开文件夹macrodemo,然后新建一个Cargo。toml文件,定义工作空间,内容如下〔workspace〕members〔hellomacro,hellomacroderive,pancakes,〕 在macrodemo目录下下分别用命令行执行cargonewhellomacrolibcargonewhellomacroderivelibcargonewpancakes 其中hellomacro是定义HelloMacrotrait的包,hellomacroderive是定义派生宏的包,pancakes是使用派生宏的包。 在hellomacro的lib。rs里定义HelloMacrotraitpubtraitHelloMacro{fnhellomacro();} 修改hellomacroderive的Cargo。toml如下,其中两个依赖是用来解析和生成TokenStream的。〔package〕namehellomacroderiveversion0。1。0edition2021Seemorekeysandtheirdefinitionsathttps:doc。rustlang。orgcargoreferencemanifest。html〔lib〕procmacrotrue〔dependencies〕syn1。0。84quote1。0。14 在hellomacroderive里的lib。rs里创建派生宏如下usecrate::procmacro::TokenSusequote::〔procmacroderive(HelloMacro)〕pubfnprocmacroderive(input:TokenStream)TokenStream{letastsyn::parse(input)。unwrap();implhellomacro(ast)}fnimplhellomacro(ast:syn::DeriveInput)TokenStream{letnameast。letgenquote!{implHelloMacroforname{fnhellomacro(){println!(Hello,Macro!Mynameis{},stringify!(name));}}};gen。into()} 其中procmacro是编译器用来读取和操作我们Rust代码的API;syncrate将字符串中的Rust代码解析成为一个可以操作的数据结构;quote则将syn解析的数据结构转换回Rust代码。首先调用syn::parse(input)。unwrap()将输入的代码解析为抽象语法树,它的部分结构如下:DeriveInput{snipident:Ident{ident:Pancakes,span:0bytes(95。。103)},data:Struct(DataStruct{structtoken:Struct,fields:Unit,semitoken:Some(Semi)})} 以上是当我们有一个自定义结构体Pancakes,并在上面标注我们定义的派生宏时解析成的。 然后定义了一个方法implhellomacro,读取抽象语法树并生成我们需要的代码。ast。ident就是被标注该宏的结构体的名字。quote!宏让我们可以编写希望返回的Rust代码。quote!宏执行的直接结果并不是编译器所期望的并需要转换为TokenStream。为此需要调用into方法,它会消费这个中间表示(intermediaterepresentation,IR)并返回所需的TokenStream类型值。 这个宏也提供了一些非常酷的模板机制;我们可以写name,然后quote!会以名为name的变量值来替换它。你甚至可以做一些类似常用宏那样的重复代码的工作。我们期望我们的过程式宏能够为通过name获取到的用户注解类型生成HelloMacrotrait的实现。该trait的实现有一个函数hellomacro,其函数体包括了我们期望提供的功能:打印Hello,Macro!Mynameis和注解的类型名。 接着在pancakes包的Cargo。toml里添加我们创建的前两个包为依赖:〔package〕namepancakesversion0。1。0edition2021Seemorekeysandtheirdefinitionsathttps:doc。rustlang。orgcargoreferencemanifest。html〔dependencies〕hellomacro{path。。hellomacro}hellomacroderive{path。。hellomacroderive} 最后在其main。rs里使用自定义的派生宏:usehellomacroderive::HelloMusehellomacro::HelloM〔derive(HelloMacro)〕structPancakes{}fnmain(){Pancakes::hellomacro();} 运行cargorun,会打印Hello,Macro!MynameisPancakes。可以看出我们自定义的宏已经为结构体Pancakes自动实现了HelloMacrotrait的关联函数hellomacro。属性宏 属性宏与自定义derive宏(派生宏)类型:允许创建新的属性但是不为derive属性生成代码 属性宏更加灵活:derive宏只能用于结构体和枚举属性宏可以用于任意条目,如函数 例子,可以创建一个名为route的属性用于注解web应用程序框架(webapplicationframework)的函数:〔route(GET,)〕fnindex(){} 其宏定义的函数签名看起来像这样:〔procmacroattribute〕pubfnroute(attr:TokenStream,item:TokenStream)TokenStream{} 其中attr是(GET,),item对应index函数。 类属性宏与自定义派生宏工作方式一致:创建procmacrocrate类型的包并实现希望生成代码的函数!函数宏 函数宏是类似于函数调用的宏,但它比普通函数更加灵活。函数宏可以接收TokenStream作为参数。与上面两种宏一样,在定义中使用Rust代码来操作TokenStream,例如我们想定义一个解析SQL语句的宏:〔procmacro〕pubfnsql(input:TokenStream)TokenStream{ 上面的宏就可以这么使用sql!()letsqlsql!(SELECTFROMpostsWHEREid1);