我的类型为Foo
:
pub struct Foo { ... }
现在,我想创建一个程序宏来创建该结构的实例。这可能涉及繁重的计算,文件访问或其他仅程序宏可以执行的操作,但是如何创建该实例的确切细节在这里并不重要。
我这样定义了程序宏:
#[proc_macro]
pub fn create_foo(_: TokenStream) -> TokenStream {
let foo_value: Foo = /* some complex computation */;
// TODO: return `foo_value`
}
我的程序宏的用户应该可以这样写:
fn main() {
let a: Foo = create_foo!();
}
请注意,Foo
可能包含很多数据,例如许多兆字节的Vec
数据。
如何从程序宏返回Foo
值?
答案 0 :(得分:1)
虽然这似乎是一个简单的请求,但实际上有很多事情可以展开。
最重要的是,了解程序宏仅返回令牌(即Rust代码)至关重要。坦率地说:Rust编译器执行您的程序宏,获取结果标记并将其粘贴到您的程序宏调用所在的用户代码中。您可以将程序宏视为预处理步骤,该步骤接受您的Rust代码,对其进行转换并吐出另一个.rs
文件。然后将那个文件提供给编译器。
为了“返回值Foo
”,您必须返回一个TokenStream
,该值表示一个表达式,其结果为Foo
。例如:
#[proc_macro]
pub fn create_foo(_: TokenStream) -> TokenStream {
quote! { Foo { data: vec![1, 2, 3] } }
}
在用户的板条箱中:
let a: Foo = create_foo!();
将扩展为:
let a: Foo = Foo { data: vec![1, 2, 3] };
data: vec![1, 2, 3]
部分可以由程序宏动态生成。如果您的Foo
实例很大,则创建该实例的代码也可能很大。这意味着编译时间可能会增加,因为Rust编译器必须解析并检查这个庞大的表达式。
所以您不能直接返回值?否。您可能认为可以使用unsafe
代码来做到这一点。例如,将一个大的const DATA: &[u8] = ...;
发出,然后mem::transmute
发出到Foo
,但是由于以下几个原因,您不能这样做:
Foo
在内存中的表示方式。相同的Foo
实例在程序宏和用户创建程序的内存中可能以不同的方式表示,因此您无法transmute
。Foo
包含堆分配的结构(Vec
),则无论如何都无法执行。 如果必须在过程宏中生成值,则只有一种解决方案可将其提供给用户,但这不是最佳选择。另外,也许在运行时进行一次计算还不错。