如何在类似函数的程序宏中计算类型的实例并返回它?

时间:2019-11-18 18:33:57

标签: rust rust-macros rust-proc-macros

我的类型为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值?

1 个答案:

答案 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,但是由于以下几个原因,您不能这样做:

  • 程序宏和用户的条板箱可能不在同一平台(CPU,OS等)上运行,这可能会影响Foo在内存中的表示方式。相同的Foo实例在程序宏和用户创建程序的内存中可能以不同的方式表示,因此您无法transmute
  • 如果Foo包含堆分配的结构(Vec),则无论如何都无法执行。

如果必须在过程宏中生成值,则只有一种解决方案可将其提供给用户,但这不是最佳选择。另外,也许在运行时进行一次计算还不错。