宏编程-quote

程序员咋不秃头2024-05-14 17:31:37  143

0x00 开篇

前面用了三篇文章来介绍 Rust 过程宏中的使用方法,在编写过程宏时,我们都是将 TokenStream 转换成字符串,然后对代进行处理。为了更方便的解析 TokenStrem,我们可以借助两个工具—— quote 和 syn。本篇文章将介绍 quote 的使用方法,本篇文章预计阅读时长 10 分钟

0x01 什么是 quote?

上篇文章了解了使用 syn 解析 Rust 代码,如果我们 将解析的代码再生成 Rust 代码,这样的步骤还是很繁琐,要解决这个问题我们可以使用 quote 这个库。quote crate 是一个可以把 Rust 代码作为数据处理的库。它提供了一种在运行时操作和生成 Rust 代码的方法,quote 也成为 Rust 元编程和代码生成的强大工具。

0x02 quote 的语法

我们主要使用 quote! 宏来在其内部编写 Rust 代码,如下:

// 模板quote! { // Rust 代码 ...}

插值

quote! 宏中可以包含插值的表达式,示例代码如下:

fn quote_test { let name = "Rust"; let expand = quote::quote! { println!("Hello, {}!", #name); }; println!("expand: {}", expand);}

在这个例子中,#name 是一个插值表达式,它会被 name 变量的值所替换。代码运行结果如下:

expand: println ! ("Hello, {}!" , "Rust") ;

注意:通过 quote! 宏返回的结果是 proc_macro2 中的 TokenStream

重复

quote! 宏允许使用 # 进行重复。在需要根据数组或集合生成一系列相似代码片段时还是比较有用的。我们可以使用 #(...)* 或 #(...),* 的语法结构用于在 quote! 宏内重复某个模式。它类似于 macro_rules! 中的重复机制。通过这种方式,可以迭代任何插值变量中的元素,并为每一个元素插入重复体的一份拷贝。详细的规则如下:

#(#var)*:对每个 var 元素重复,不带分隔符。

#(#var),*:星号(*)前的字符用作分隔符,逗号(,)用作分隔符。表示对每个 var 元素重复,并使用逗号 ( , ) 分隔符分隔。

#( struct #var; )*:还可以再重复体中包含其它内容,仍然是对每个 var 元素重复且不带分隔符。表示在每个 struct 声明后面添加分号。

#( #k => println!("{}", #v), )*:甚至可以包括多个内插操作。在这个例子中,对于每一对键值对,我们都生成了一个 println! 宏调用,并且每个调用之间用逗号分隔。

具体的示例代码如下:

#[test]fn quote_test2 { let list = vec!["a", "b", "c"]; let expand = quote::quote! { let (#(#list),*) = (1, 2, 3); }; println!("expand: {}", expand);}

代码运行结果如下:

expand: let ("a" , "b" , "c") = (1 , 2 , 3) ;

嵌套

quote! 宏也支持嵌套使用,可以在 quote! 宏内部使用另一个quote! 宏。示例代码如下:

#[test]fn quote_test3 { let name = "Rust"; let expand = quote::quote! { quote::quote! { println!("Hello, {}!", #name); } }; println!("expand: {}", expand);}

代码的运行结果如下:

expand: quote :: quote ! { println ! ("Hello, {}!" , "Rust") ; }

标识符

在有些场景下,我们可能需要动态生成变量名或函数名。quote! 宏可以可以结合和 proc_macro2 提供的 Ident::new 实现这一点,示例代码如下:

#[test]fn quote_test4 { let func_name = Ident::new("test_function", Span::call_site); let expand = quote::quote! { fn #func_name { println!("这是一个动态命名的函数。"); } }; println!("expand: {}", expand);}

首先我们使用 proc_macro2 库中的 Ident::new 函数创建了一个新的标识符 Ident,它代表了一个函数名。test_function 就是是这个函数的名称。 Span::call_site 则表示这个标识符应该被视为在宏调用点(宏被调用的地方)生成。

0x03 在过程宏中使用 quote

以第 25 课的派生宏为例,使用 syn 和 quote 生成派生宏的示例代码如下:

#[proc_macro_derive(MyDebug)]pub fn custom(input: TokenStream) -> TokenStream { // 派生宏的处理逻辑 let ast = parse_macro_input!(input as DeriveInput); // 结构体名称 TokenStream let struct_token_stream = ast.ident.to_token_stream; // 名称字符串 let struct_name = struct_token_stream.to_string; let expand = quote::quote! { // 在代码中需要使用 TokenStream impl #struct_token_stream { fn my_debug(&self) {{ println!("{} 自定义的派生宏!", #struct_name); }} } }; expand.into}

首先使用 parse_macro_input! 宏将输入的 TokenStream 解析成 AST(抽象语法树)中的 DeriveInput 结构。从 AST 结点 ast 中提取出结构体的标识符(即结构体的名字),并将这个标识符转换为 TokenStream。然后通过 quote! 生成 Rust 代码。通过使用 syn 和 quote 这两个库会让我们写的 Rust 过程宏非常清晰易懂。

0x04 小结

可以看到 syn 和 quote 在编写过程宏中尤为重要,它们的这些特性已经成为不可或缺的工具。使用 syn 和 quote 不仅能够简化代码生成的流程,使代码清晰易懂,还可以提高开发效率。

转载此文是出于传递更多信息目的。若来源标注错误或侵犯了您的合法权益,请与本站联系,我们将及时更正、删除、谢谢。
https://www.414w.com/read/554520.html
0
随机主题
0-3! 奥预赛黑马惨败, NO.2被横扫, 亚洲首败, 史诗级决胜局诞生国际生物多样性日丨如何携手共建万物共生的美丽家园?一公里油耗才6毛钱 开瑞优劲 致富又带劲山西: 科学预防“干热风” 确保小麦丰产丰收男单爆大冷! 男单世界冠军2-3日本选手, 无缘开门红, 球迷很意外泽连斯基介绍西方套路: 西方援乌看似前进了一步, 但提前退了两步中肯! 鲁德点评今年法网夺冠热门! 支持德约科维奇的原因很牵强!G1东欧爆种, 不然想晋级都难? 森林狼绅士横扫独行侠不在话下普京访华圆满结束,临行前对华再表态,特朗普斥责拜登政府无能!快递站里、电视里、超市小票里都能见到, 为了这件事, 杭州桐庐消防拼了中俄多个大动作落地,美债连续三个月缩减,美联储发现绷不住了千元档王炸, vivo Y200 GT: 旗舰同款大电池, 重新定义Y系列~虎牢关时期, 谁能抵挡吕布100招? 仅2人可以, 关羽张飞赵云都不行1894年, 18岁珍妃因得罪慈禧, 惨遭扒裤羞辱, 激烈反抗终致命丧黄泉夏威夷是如何变成美国的第五十个州的,美国的第一次干涉别国内政“谁还不喜欢会炸毛的车呢?”南海交锋,外军4打2,解放军战机遭火控雷达锁定,现场惊心动魄一个更危险的情况, 正向乌克兰逼近!苹果手机尾插口不好用怎么办?你先别急着换,看完视频再打算!步行者第一场太可惜了,煮熟的鸭子飞了伍尔特: 2023财年销售额204亿欧元, 新增超过1, 400名员工
最新回复(0)