【问题标题】:How can I compute an instance of a type in a function-like procedural macro and return it?如何在类似函数的过程宏中计算类型的实例并返回它?
【发布时间】:2020-03-14 04:39:13
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

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

    最重要的是,了解过程宏只返回 tokens(即 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,但由于以下几个原因你不能:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-21
      • 1970-01-01
      • 2020-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-04
      • 1970-01-01
      相关资源
      最近更新 更多