Rust学习笔记(五十九)宏
宏在Rust里是指一组相关特性的集合称谓:
使用macro_rules!构建的声明宏(declarative macro)3种过程宏自定义#[derive]宏,用于struct或enum,可以为其指定随derive属性添加的代码类似属性的宏,可以在任意条目上添加自定义属性类似于函数的宏,看起来像函数调用,对其指定为参数的token进行操作macro_rules! 中有一些奇怪的地方。在将来,会有第二种采用 macro 关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,macro_rules! 实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数 Rust 程序员 使用 宏而非 编写 宏的事实,此处不再深入探讨 macro_rules!。请查阅在线文档或其他资源,如 “The Little Book of Rust Macros(https://danielkeep.github.io/tlborm/book/index.html)” 来更多地了解如何写宏。
函数与宏的差别从本质上看,宏是用来编写可以生成其它代码的代码(元编程,metaprograming)。 函数在定义签名时,必须声明参数的个数和类型,而宏可以处理可变的参数。编译器会在解释代码前展开宏。宏的定义比函数复杂得多,难以阅读、理解和维护。在某个文件调用宏时,必须提前定义宏或将宏引入当前作用域。而函数可以在任何位置定义并在任何位置使用。
基于属性来生成代码的过程宏这种形式更像函数(某种形式的过程)一些。它会接收并操作输入的Rust代码,并生成另外一些Rust代码作为结果。
三种过程宏:
自定义派生属性宏函数宏创建过程宏时,宏定义必须单独放在它们自己的包中,并且使用特殊的包类型。例:
//src/lib.rsuse proc_macro;#[some_attribute]//some_attribute 是一个使用特定宏的占位符。pub fn some_name(input: TokenStream) -> TokenStream {//TokenStream 类型由包含在 Rust 中的 proc_macro crate 定义并表示token序列。 这是宏的核心:宏所操作的源代码构成了输入 TokenStream,宏产生的代码是输出 TokenStream。}自定义derive宏(派生宏)需求:
创建一个hello_macro library包,定义一个拥有关联函数hello_macro的HelloMacro trait我们定义一个能自动实现该trait的过程宏只需要在对应的类型上标准#[derive(HelloMacro)],就能得到hello_macro函数的默认实现首先新建并打开文件夹macro_demo,然后新建一个Cargo.toml文件,定义工作空间,内容如下
[workspace]members = [ "hello_macro", "hello_macro_derive", "pancakes",]在macro_demo目录下下分别用命令行执行
> cargo new hello_macro --lib> cargo new hello_macro_derive --lib> cargo new pancakes其中hello_macro是定义HelloMacro trait的包,hello_macro_derive是定义派生宏的包,pancakes是使用派生宏的包。
在hello_macro的lib.rs里定义HelloMacro trait
pub trait HelloMacro { fn hello_macro();}修改hello_macro_derive的Cargo.toml如下,其中两个依赖是用来解析和生成TokenStream的。
[package]name = "hello_macro_derive"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[lib]proc-macro = true[dependencies]syn = "1.0.84"quote = "1.0.14"在hello_macro_derive里的lib.rs里创建派生宏如下
extern crate proc_macro;use crate::proc_macro::TokenStream;use quote::quote;use syn;#[proc_macro_derive(HelloMacro)]pub fn proc_macro_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); impl_hello_macro(&ast)}fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { impl HelloMacro for #name { fn hello_macro() { println!("Hello, Macro! My name is {}", stringify!(#name)); } } }; gen.into()}其中proc_macro是编译器用来读取和操作我们 Rust 代码的 API;syn crate 将字符串中的 Rust 代码解析成为一个可以操作的数据结构;quote 则将 syn 解析的数据结构转换回 Rust 代码。 首先调用syn::parse(input).unwrap()将输入的代码解析为抽象语法树,它的部分结构如下:
DeriveInput { // --snip-- ident: Ident { ident: "Pancakes", span: #0 bytes(95..103) }, data: Struct( DataStruct { struct_token: Struct, fields: Unit, semi_token: Some( Semi ) } )}以上是当我们有一个自定义结构体Pancakes,并在上面标注我们定义的派生宏时解析成的。
然后定义了一个方法impl_hello_macro,读取抽象语法树并生成我们需要的代码。&ast.ident就是被标注该宏的结构体的名字。 quote! 宏让我们可以编写希望返回的 Rust 代码。quote! 宏执行的直接结果并不是编译器所期望的并需要转换为 TokenStream。为此需要调用 into 方法,它会消费这个中间表示(intermediate representation,IR)并返回所需的 TokenStream 类型值。
这个宏也提供了一些非常酷的模板机制;我们可以写 #name ,然后 quote! 会以名为 name 的变量值来替换它。你甚至可以做一些类似常用宏那样的重复代码的工作。 我们期望我们的过程式宏能够为通过 #name 获取到的用户注解类型生成 HelloMacro trait 的实现。该 trait 的实现有一个函数 hello_macro ,其函数体包括了我们期望提供的功能:打印 Hello, Macro! My name is 和注解的类型名。
接着在pancakes包的Cargo.toml里添加我们创建的前两个包为依赖:
[package]name = "pancakes"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]hello_macro = {path = "../hello_macro"}hello_macro_derive = {path = "../hello_macro_derive"}最后在其main.rs里使用自定义的派生宏:
use hello_macro_derive::HelloMacro;use hello_macro::HelloMacro;#[derive(HelloMacro)]struct Pancakes{}fn main() { Pancakes::hello_macro();}运行cargo run,会打印Hello, Macro! My name is Pancakes。可以看出我们自定义的宏已经为结构体Pancakes自动实现了HelloMacro trait的关联函数hello_macro。
属性宏属性宏与自定义derive宏(派生宏)类型:
允许创建新的属性但是不为derive属性生成代码属性宏更加灵活:
derive宏只能用于结构体和枚举属性宏可以用于任意条目,如函数例子,可以创建一个名为 route 的属性用于注解 web 应用程序框架(web application framework)的函数:
#[route(GET, "/")]fn index() {}其宏定义的函数签名看起来像这样:
#[proc_macro_attribute]pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}其中attr是(GET, "/"),item对应index函数。
类属性宏与自定义派生宏工作方式一致:创建 proc-macro crate 类型的包并实现希望生成代码的函数!
函数宏函数宏是类似于函数调用的宏,但它比普通函数更加灵活。函数宏可以接收TokenStream作为参数。与上面两种宏一样,在定义中使用Rust代码来操作TokenStream,例如我们想定义一个解析SQL语句的宏:
#[proc_macro]pub fn sql(input: TokenStream) -> TokenStream {上面的宏就可以这么使用sql!()
let sql = sql!(SELECT * FROM posts WHERE id=1);免责声明:本文由用户上传,与本网站立场无关。财经信息仅供读者参考,并不构成投资建议。投资者据此操作,风险自担。 如有侵权请联系删除!
-
大众CC作为一款备受关注的中型轿车,凭借其优雅的设计和出色的性能一直吸引着众多消费者的目光。2025款大众CC...浏览全文>>
-
2025款阜阳途锐新车正式上市,凭借其卓越的性能和豪华配置吸引了众多消费者的关注。这款车型以最低售价55 88...浏览全文>>
-
在准备购买一辆汽车之前,了解车辆的落地价格是非常重要的。所谓落地价,是指购车时除了车款之外还需要支付的...浏览全文>>
-
安徽淮南地区的长安启源E07作为一款备受关注的新能源车型,凭借其时尚的设计、丰富的配置以及出色的续航能力,...浏览全文>>
-
安徽淮南长安启源A05 2025款新车现已正式上市,这款车型以其高性价比和出色性能吸引了众多消费者的关注。作为...浏览全文>>
-
安徽阜阳地区的威然车型在近期进行了配置上的升级,对于想要购买这款MPV的消费者来说,这是一个值得关注的消息...浏览全文>>
-
随着汽车市场的不断发展,SUV车型因其宽敞的空间和多功能性受到了越来越多消费者的青睐。作为大众旗下的高端旗...浏览全文>>
-
安徽蚌埠地区想要购买长安启源E07这款新能源汽车的朋友,可以参考以下信息来做出更明智的选择。长安启源E07定...浏览全文>>
-
随着汽车市场的不断发展,2025款安庆高尔夫作为一款备受关注的车型,其价格和配置自然成为消费者热议的话题。...浏览全文>>
-
近期,安徽蚌埠地区的帕萨特车型迎来了新一轮的价格调整,其落地价再次创下新低,吸引了众多消费者的关注。作...浏览全文>>
- 悉尼最后几个年薪低于 10 万美元的郊区
- 2025 年新南威尔士州值得投资的地方
- 揭秘在澳大利亚买房需要多少收入
- 悉尼最后几个年薪低于 10 万美元的郊区
- 昆士兰有望成为澳大利亚房地产强国之一
- MSI 推出首款双模式 4K 曲面电竞显示器
- 飞利浦 Screeneo GamePix 900:在发布前进行预览
- 您会在这个奇怪的电动露营三轮车里露营吗
- Meross 推出支持 Matter 的智能恒温器
- 配备出色 3K OLED 显示屏的 Acer Swift 16 现已降价至史上最低价
- Acer Predator Helios 18 RTX 4080 游戏笔记本电脑 现优惠 725 美元
- VivoX200Pro视频和新样张揭示了200MP蔡司变焦相机的锐利眼睛可以达到多远
- 派对氛围天文爱好者又一次欣赏到极光秀
- iPhone16相机控制按钮有史以来最不苹果的东西
- 贾雷尔夸萨与利物浦签订新合同
- 首款在安兔兔上得分300万的手机拥有非常强大的SoC即将发布
- HumaninMotionRobotics的自平衡XoMotion外骨骼获得加拿大批准用于物理治疗
- 龙宫样本对之前关于富碳小行星形成的观点提出了质疑
- 凯文德布劳内伤情更新曼城球星的伤势进展和可能的回归日期
- 实验室实验表明用核武器轰炸一颗巨大的小行星可以拯救地球