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