1

我有类型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从我的程序宏中返回值?

4

1 回答 1

2

虽然这似乎是一个简单的请求,但实际上有很多事情要展开。

最重要的是,了解过程宏仅返回标记(即 Rust 代码)至关重要。坦率地说:Rust 编译器执行您的过程宏,获取生成的标记并将它们粘贴到您的过程宏调用所在的用户代码中。您可以将过程宏视为一个预处理步骤,它获取您的 Rust 代码,对其进行转换并输出另一个.rs文件。然后将该文件提供给编译器。


为了“返回值Foo”,您必须返回TokenStream表示计算结果为的表达式的 a 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代码来做到这一点。例如,发出一个 bigconst DATA: &[u8] = ...;mem::transmute它到Foo,但你不能有几个原因:

  • 程序宏和用户的 crate 可能不会在同一平台(CPU、OS、...)上运行,这都可能影响Foo在内存中的表示方式。对于您的程序宏和您的用户 crate,相同的Foo实例在内存中的表示方式可能不同,因此您不能 transmute.
  • 如果Foo包含堆分配结构 ( Vec),则无论如何您都不能这样做。

如果您必须在程序宏中生成值,那么只有一种解决方案可以将其提供给用户,但这并不是最优的。或者,也许在运行时计算一次并不是那么糟糕。

于 2019-11-18T20:23:25.743 回答