宏编程-quote

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

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
随机主题
张兰曝孙子被退学,总不去上课、没人管他、不做作业惹众怒! 南通支云转争议文章, 内涵泰山申花球迷, 遭球迷集体抵制数学王子:高斯的平凡出身与非凡之旅单车月销3万台, 如今面临困境, 雪佛兰探界者plus能否挽回局面?我国发布全球首个开源大规模片上互联网络 IP“温榆河”因“孩子随父姓”被群嘲, papi酱的回应很霸气, 网友看完拍手叫好布局5G导致千亿资金打水漂? 外籍院士揭露真实的状态!助力乡村共同富裕! 第十六届浙江一市·宁海白枇杷文化活动举行内地封杀的女星, 被岛国拍出来了庆余年2唯一输家: 最牛星二代跌下神坛, 演技尴尬, 全程被吊打引诱到桥上戏耍,瞬间攻守颠倒!故障率最低0.00064! 广汽、长安、吉利等, 这五款家轿选谁好?团战开黑不卡顿? 直播追剧无延迟? 这吐血的网速终于让锐捷给我冲了!张凌赫徐若晗吻戏曝光, 何医生霸总感撩动全网, 你准备好接招了吗他演女人竟骗过所有人, 扮女人扮成他这样, 全世界找不到第二个创新新材: 5月22日召开业绩说明会, 投资者参与田纳西 vs. 圣彼得:2024 NCAA中国男篮归化爆发 李凯尔成森林狼头号奇兵 替补12分钟7中5太抢镜泰消保风险提示: 利率下行时期, 这样选择保险, 稳稳守住你钱袋子TES有救了? 涵艺: 前EDG教练茂凯将加入TES! 教练组均将重新洗牌明天会更好, 尤文多位球员开启续约计划, 门将补强盯上国米青训
最新回复(0)