4

我正在创建一个从某个配置文件自动生成库的程序宏(它是一个寄存器布局,但这对于这个问题并不重要)。

我希望该库自动生成自动库随附的文档,并包含应该使用cargo test. 现在,我已经实现了大部分,但有一个问题我看不到解决方案。

假设我们有一个调用的库my_lib,我们在其中调用宏来填充它:

use my_macro_lib::hello;

hello!();

扩展为:

/// `foo` will always return `true`
/// ```
/// use my_lib;
/// assert!(my_lib::foo());
/// ```
pub fn foo() -> bool {
    true
}

这将按预期运行 -cargo doc将做正确的事情cargo test并按预期运行文档测试。

问题是,在这个例子中,use my_lib被硬编码到my_macro_lib这显然是不可取的。

如何创建一个宏来推断正在调用的 crate 的名称?

我尝试macro_rules!在程序宏内部使用 a expand $crate,但这违反了卫生规则。

4

1 回答 1

6

CARGO_PKG_NAME您可以通过读取环境变量来获取正在使用您的宏的 crate 的名称。请注意,您必须通过 std::env(在宏的“运行时”)而不是通过env!(在编译 proc 宏箱时)来阅读它。

#[proc_macro]
pub fn hello(input: TokenStream) -> TokenStream {
    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
    let use_statement = format!("use {}::foo;", crate_name);

    let output = quote! {
        /// `foo` will always return `true`
        /// ```
        #[doc = #use_statement]
        /// assert!(foo());
        /// ```
        pub fn foo() -> bool {
            true
        }
    };

    output.into()
}

这里有一些关于在文档注释中插入内容的复杂性。插入like/// #an_ident不起作用,因为文档注释是以特殊方式解析的。我们可以做到这一点的唯一方法是创建一个字符串并使用#[doc = ...]语法。这有点烦人,因为您必须在quote!调用之前创建字符串,但它确实有效。

但是,我认为这不能保证有效。目前 proc 宏可以访问所有环境(包括文件系统、网络等)。据我所知,proc 宏不能保证此访问权限,并且 proc 宏将来可能会被沙盒化。所以这个解决方案还不是完美的,但它现在可以工作(并且可能还有相当长的一段时间)。


另一种方法是让用户将 crate 名称传递给您的宏:

hello!(my_lib);

如果每个 crate 只调用一次宏,这可能是首选解决方案。如果你的宏被大量调用,那么重复 crate 名称可能会很烦人。

于 2019-10-29T11:40:26.547 回答