2个不稳定版本
0.2.0 | 2023年1月7日 |
---|---|
0.1.0 | 2023年1月6日 |
1060 在 过程宏 中排名
1,123 每月下载次数
用于 5 个crate(其中3个直接使用)
8KB
快速代码生成宏
通过引用轻松高效地生成代码
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
quote_into!(stream += println!("hello world!"));
assert_eq!(stream.to_string(), "println ! (\"hello world!\")");
变量插值
您可以使用变量的名称(以 #
为前缀)插值任何实现 ToTokens
的值。
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
let variable = false;
quote_into!(stream += let boolean = #variable;);
assert_eq!(stream.to_string(), "let boolean = false ;");
插值块
您可以使用 #{}
插入Rust代码块。在内部,您可以进行函数调用、使用条件语句或循环,或任何其他允许在代码块中进行的操作。
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut stream = TokenStream::new();
quote_into!(stream += let array = [#{
for i in 0..3 {
quote_into!(stream += Some(#i),)
}
}];);
assert_eq!(stream.to_string(), "let array = [Some (0i32) , Some (1i32) , Some (2i32) ,] ;");
注意:如果您有引用标记中的组(如 {}
、()
或 []
),将创建一个新的 TokenStream
。在上面的示例中,外部的 stream
与 #{}
插值中的 stream
不相同,因为它被 []
包装。
表达式插值
您还可以使用 #()
插值任何Rust表达式。
use proc_macro2::TokenStream;
use quote_into::quote_into;
let mut s = TokenStream::new();
quote_into!(s += const ANSWER: u32 = #(7 * 6););
assert_eq!(s.to_string(), "const ANSWER : u32 = 42i32 ;");
与 quote
的比较
代码生成的既定标准是 quote
crate。这个crate对其API和性能提出了一些小的改进。
API
当涉及到循环插值时,Quote似乎在很大程度上受到了 macro_rules
的启发。虽然表达力强且简洁,但在我看来,它们学习起来和阅读起来有点困难。
相反,quote_into
提供了一个简单的“插值块” #{ }
,您可以在其中编写常规的Rust代码。由于基本上没有额外的语法,使用插值块应该感觉更直观。
性能
quote
返回一个新的 TokenStream
,而 quote_into
接受一个 TokenStream
以向其中追加标记。虽然这使得 quote
的代码稍微简洁一些,但在组合引用片段时,效率略低。通常,代码生成发生在不同的函数中,然后组合在一起。推荐的方法是创建中间的 TokenStream
,并使用 #
进行插值。
use quote::quote;
let type_definition = quote! {...};
let methods = quote! {...};
let tokens = quote! {
#type_definition
#methods
};
即使您只创建了几个 TokenStreams
并将它们组合,额外的分配也会对性能产生轻微的影响。更极端的情况是深度递归函数,它反复构建和插值许多标记流。例如,考虑以下函数,它生成如下形式的代码 Box<Box<...<bool>>>
use quote::quote;
use quote_into::quote_into;
use proc_macro2::TokenStream;
// Using quote:
fn quote_box_wrap(levels: usize) -> TokenStream {
match levels {
0 => quote!(bool),
other => {
let inner = quote_box_wrap(other - 1);
quote!(Box<#inner>)
}
}
}
// Using quote_into:
fn quote_into_box_wrap(levels: usize) -> TokenStream {
fn inner(s: &mut TokenStream, levels: usize) {
match levels {
0 => quote_into!(s += bool),
other => quote_into! {s += Box < #{
inner(s, other - 1)
} >},
}
}
// there's a bit of boilerplate, but only once per macro
let mut s = TokenStream::new();
inner(&mut s, levels);
s
}
对于 levels=100
的结果
quote: 591.14 µs
quote_into: 17.247 µs
由于使用 quote
需要创建许多中间 TokenStreams
,因此在这种情况下速度较慢。这又是一个极端的例子。除非您像上面那样不高效地嵌套多个插值的 TokenStreams
多层,否则切换到 quote_into
后,代码生成速度不太可能提高 30 倍。
状态:原型
虽然您可以在项目中技术上使用 quote_into
,但它目前处于原型阶段。
- API 可能会更改,因为我对一些事情还不确定。
- 它是使用
quote
实现的...我不确定这有多“糟糕”(它可能会增加编译时间),但我有一种感觉,它应该在某个时候使用手动标记生成代码进行重写。
依赖项
~1.5MB
~35K SLoC