有没有一种方法可以在proc-macro crate中拥有公共trait?

5
我有一个proc-macro库,其中包含一个宏,在展开时需要使用Rust内置类型的自定义trait实现。我尝试在同一个库中定义该trait,但是Rust告诉我proc-macro库只能有公共宏(用#[proc_macro]注释的函数),除此之外不能有其他公共内容。因此,我将该trait放入另一个库中,并在proc-macro库中将其作为依赖项包含进来。但这意味着任何想要使用我的proc-macro库的人也必须依赖于其他trait库。
所以我想知道是否有一种方法可以向proc-macro库添加公共trait,或以某种方式使proc-macro和trait库链接在一起,以便最终用户无法仅使用其中一个?如果两者都不可能,唯一的解决方案就是记录依赖关系,这有点脆弱。
2个回答

10
通常处理这个问题的方法是不让用户完全依赖于您的过程宏库。您可以使用3个包来解决问题:
- “internal”包含类型和特征定义,由proc-macro使用。 - proc-macro包: - 依赖于内部包,因此可以使用其类型和特征。 - “public”包: - 依赖于内部和proc-macro包。 - 重新导出您希望用户使用的所有类型、特征和宏。
每当您的宏在其生成的代码中提到共享类型时,您需要使用完全限定的名称,以使用户不需要导入它们。

这种模式在实际应用中有一些流行的例子:

  • thiserror 依赖于包含实际宏的 thiserror-impl
  • pin-project 依赖于再次包含宏的 pin-project-internal
  • darling 依赖于 darling-coredarling-macro,它本身也依赖于 darling-core

谢谢这个信息,它解决了我对于重新导出宏的一些疑惑。但是我认为,如果我的宏扩展将"use trait_crate::MyTrait"插入到客户端源代码中,那么他们也必须直接依赖于"trait_crate"包,对吗? - jecolon
不必如此,您可以让您的宏使用完全限定路径。我会更新答案以添加这一点。 - Peter Hall
1
非常感谢!Rust 太棒了!=) - jecolon
proc-macro-crate 是一个有用的工具,可以获取完全限定路径,以防用户别名该 crate。 - Mingwei Samuel

1

同意@PeterHall的回答。

我对这个帖子的回复并不是对它本身的回答,而是关于为什么通常需要至少3个crate的一些额外信息。这是由于涉及到proc-macro crate的以下4个限制。

  1. 一个proc-macro必须在自己的crate中定义。

References:
https://developerlife.com/2022/03/30/rust-proc-macro/
https://towardsdatascience.com/nine-rules-for-creating-procedural-macros-in-rust-595aa476a7ff

通常在编写过程宏时使用的宏,如`syn`和`quote`,无法在`quote`宏扩展中引用`$crate`来表示当前的 crate。 例如,不存在这样的东西:
quote!(
    use $crate::types::AStruct;
);

给出扩展的代码如下:
::crate::types::AStruct;

3. 你不能有循环依赖,即创建物 A 依赖于创建物 B,而创建物 B 又依赖于创建物 A。
4. proc-macro 创建物无法导出内部类型,例如 pub mod MyMod; pub use MyMod::MyStruct; 是不允许的。这就是为什么你需要一个“内部”创建物和一个“公共”创建物。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接