如何跨模块文件使用宏?

Posted

技术标签:

【中文标题】如何跨模块文件使用宏?【英文标题】:How do I use a macro across module files? 【发布时间】:2014-12-31 02:42:27 【问题描述】:

我在同一个 crate 中的不同文件中有两个模块,其中 crate 启用了 macro_rules。我想在另一个模块中使用一个模块中定义的宏。

// macros.rs
#[macro_export] // or not? is ineffectual for this, afaik
macro_rules! my_macro(...)

// something.rs
use macros;
// use macros::my_macro; <-- unresolved import (for obvious reasons)
my_macro!() // <-- how?

我目前遇到了编译器错误“macro undefined: 'my_macro'”...这是有道理的;宏系统在模块系统之前运行。我该如何解决这个问题?

【问题讨论】:

你不应该使用module::my_macro!()? nope (not afaik) - 据报道模块前缀被忽略(根据编译器消息)。 【参考方案1】:

这个答案在 Rust 1.1.0-stable 中已经过时了。


您需要在macros.rs 的顶部添加#![macro_escape] 并使用mod macros; 将其包含在Macros Guide 中。

$ cat macros.rs
#![macro_escape]

#[macro_export]
macro_rules! my_macro 
    () =>  println!("hi"); 


$ cat something.rs
#![feature(macro_rules)]
mod macros;

fn main() 
    my_macro!();


$ rustc something.rs
$ ./something
hi

供日后参考,

$ rustc -v
rustc 0.13.0-dev (2790505c1 2014-11-03 14:17:26 +0000)

【讨论】:

我完全错过了这个属性。谢谢! 顺便说一句,#[macro_export] 属性在这里是不必要的。仅当宏应导出到外部 crate 用户时才需要。如果宏只在 crate 内部使用,则不需要#[macro_export] 非常感谢您的回答。我只想补充一点,如果您的something.rs 文件使用其他模块,例如mod foobar;,并且此foobar 模块使用来自macro.rs 的宏,那么您必须将mod macro; 放在前面 mod foobar; 用于编译程序。小事,但这不是一个明显的 IMO。 (n.b. 这个答案现在已经过时了;我已经接受了 Lukas 给出的最新答案)【参考方案2】:

同一个 crate 中的宏

新方法(自 Rust 1.32,2019-01-17)

foo::bar!();  // works

mod foo 
    macro_rules! bar 
        () => ()
    

    pub(crate) use bar;    // <-- the trick


foo::bar!();  // works

使用pub use,可以像使用和导入任何其他项目一样使用和导入宏。并且与旧方法不同,这不依赖于源代码顺序,因此您可以在宏定义之前(源代码顺序)使用它。

旧方法

bar!();   // Does not work! Relies on source code order!

#[macro_use]
mod foo 
    macro_rules! bar 
        () => ()
    


bar!();    // works

如果你想在同一个 crate 中使用宏,定义你的宏的模块需要属性#[macro_use]。请注意,宏只能在定义后使用!


跨箱子的宏

箱子util

#[macro_export]
macro_rules! foo 
    () => ()

箱子user

use util::foo;

foo!();

请注意,使用此方法,宏始终位于 crate 的顶层!所以即使foo 会在mod bar 中,user 板条箱仍然必须写use util::foo; use util::bar::foo;。通过使用pub use,您可以从 crate 的模块中导出宏(除了在根目录中导出)。

在 Rust 2018 之前,您必须通过将属性 #[macro_use] 添加到 extern crate util; 语句来从其他 crate 导入宏。这将从util 导入所有宏。这种语法应该不再需要了。

【讨论】:

"宏只能在定义后才能使用。" - 这是关键,因为即使您正确完成了所有其他提到的事情,您也可能会遇到该错误。例如,如果您有模块 macrosfoo(使用来自 macros 的宏),并且您在 lib.rs 或 main.rs 中按字母顺序列出它们,则 foo 将在宏之前加载,并且代码无法编译。 ^ 专业提示 - 这完全吸引了我 还要注意,为了在内部使用宏,#[macro_use] 属性应该在每个模块和父模块等上。直到它到达你需要使用它的地步。 这个答案对我不起作用。声明宏的模块有 #[macro_use] 并且它首先在 lib.rs 中声明 - 仍然没有工作。 @Ten 的回答有所帮助,我在 lib.rs 的顶部添加了 #[macro_use] - 然后它起作用了。但我仍然不确定最佳实践是什么,因为我读过 here 说“你不从其他模块导入宏;你从定义模块导出宏” 我总是忘记 Rust 的宏是如何与模块一起工作的。这是一个糟糕的系统,希望有一天会有更好的系统。【参考方案3】:

#![macro_use] 添加到包含宏的文件顶部将导致所有宏被拉入 main.rs。

例如,假设这个文件名为 node.rs:

#![macro_use]

macro_rules! test 
    () =>  println!("Nuts"); 


macro_rules! best 
    () =>  println!("Run"); 


pub fn fun_times() 
    println!("Is it really?");

您的 main.rs 有时会如下所示:

mod node;  //We're using node.rs
mod toad;  //Also using toad.rs

fn main() 
    test!();
    best!();
    toad::a_thing();

最后假设您有一个名为 toad.rs 的文件,它也需要这些宏:

use node; //Notice this is 'use' not 'mod'

pub fn a_thing() 
  test!();

  node::fun_times();

请注意,一旦使用 mod 将文件拉入 main.rs,您的其余文件就可以通过 use 关键字访问它们。

【讨论】:

我添加了更多说明。从 rustc 1.22.1 开始,这确实有效。 你确定吗?这个 #![macro_use](不是 #[macro_use])记录在哪里?我找不到它。它在这里不起作用。 这在我发布时有效,Rust 的包含系统如此糟糕,完全有可能不再有效。 @Markus 注意#![macro_use] 语句是INSIDE宏模块,而不是外部。 #![...] 语法对应于将属性应用于其包含范围,例如#![feature(...)](如果写成 #[feature(...)] 显然这没有意义;它在语义上要求编译器在 crate 中的特定项目上启用某些功能,而不是在整个根 crate 上)。因此,正如@LukeDupin 所说,模块系统一团糟,尽管原因可能与乍一看不同。 我确实希望这个答案提到了这个结构不是完全地道的(除此之外,我喜欢这个答案)。尽管它具有(非)惯用性,但它很有趣,因为将它放在惯用形式旁边会让人很痛苦地看到,宏与模块系统的交互方式与通常的结构不同。或者它至少会散发出强烈的气味(正如@Markus 对它的抱怨所证明的那样)。【参考方案4】:

我在 Rust 1.44.1 中有 came across the same problem,此解决方案适用于更高版本(已知适用于 Rust 1.7)。

假设你有一个新项目:

src/
    main.rs
    memory.rs
    chunk.rs

ma​​in.rs中,你需要注释你是从源中导入宏,否则,它不会为你做。

#[macro_use]
mod memory;
mod chunk;

fn main() 
    println!("Hello, world!");

所以在 memory.rs 中可以定义宏,不需要注释:

macro_rules! grow_capacity 
    ( $x:expr ) => 
        
            if $x < 8  8  else  $x * 2 
        
    ;

终于可以在chunk.rs中使用了,这里不需要包含宏,因为在main.rs中完成了:

grow_capacity!(8);

upvoted answer 让我感到困惑,this doc by example 也会有帮助。


注意:此解决方案确实有效,但请注意 @ineiti 在 cmets 中突出显示,您在 main.rs/lib.rs 中声明 mods 的顺序很重要 , 在宏 mod 声明之后声明的所有 mods 尝试调用宏都会失败。

【讨论】:

@Shepmaster 赞成的答案在同一个地方定义了宏和导入语句,所以它引起了混乱(对我来说)。我在定义中使用了#[macro_use]。编译器不会说它放错地方了。 感谢您的回答!我也被接受的答案弄糊涂了,直到我读了你的解释才弄明白。 @Shepmaster 在您链接到的部分中没有提及宏是如何工作的。您的意思是链接到本书的其他部分吗? @detly 不,因为我的评论指出的内容比宏更广泛。这个回答者似乎很困惑mod ... mod some_file 是同一个东西并且都创建了一个模块。接受的答案已经显示了#[macro_use] 的用法,因此 this 答案并没有真正提供任何新内容。 还要确保modmain.rs 中的顺序正确。如果你有mod chunk; mod memory;,那么memory.rs 中的宏调用将会失败。【参考方案5】:

1.32.0(2018 版)的替代方法

请注意,虽然 the instructions from @lukas-kalbertodt 仍然是最新的并且运行良好,但必须记住宏的特殊命名空间规则的想法可能会让某些人感到烦恼。

编辑:原来他们的答案是updated to include my suggestion,没有任何信用提及?

在 2018 版及以后的版本中,自 Rust 的 1.32.0 版本以来,还有另一种方法也有效,并且恕我直言,它的好处是让教学更容易(例如,它使#[macro_use] 过时)。关键思想如下:

重新导出的宏的行为与任何其他项(函数、类型、常量、)一样:它在重新导出发生的模块内命名空间。

然后可以使用完全限定的路径来引用它。

它也可以在本地used / 引入范围,以便以非限定方式引用它。

示例

macro_rules! macro_name  ... 
pub(crate) use macro_name; // Now classic paths Just Work™

就是这样。很简单吧?


请随意继续阅读,但前提是您不害怕信息过载;)我将尝试详细说明为什么、如何以及何时准确。

更详细的解释

为了重新导出 (pub(...) use ...) 宏,我们需要引用它!这就是原始答案中的规则很有用的地方:总是可以在发生宏定义的模块中命名宏,但只能在该定义之后

macro_rules! my_macro  ... 
my_macro!(...); // OK
// Not OK
my_macro!(...); /* Error, no `my_macro` in scope! */
macro_rules! my_macro  ... 

在此基础上,我们可以在定义之后重新导出一个宏;与 Rust 中的所有其他全局项一样,重新导出的名称本身与位置无关 ?

我们可以这样做:

struct Foo 

fn main() 
    let _: Foo;

我们也可以这样做:

fn main() 
    let _: A;


struct Foo 
use Foo as A;

这同样适用于其他项目,例如函数,也适用于宏!

fn main() 
    a!();


macro_rules! foo  ...  // foo is only nameable *from now on*
use foo as a;            // but `a` is now visible all around the module scope!

事实证明,我们可以写use foo as foo;,或常见的use foo; 简写,它仍然有效。

剩下的唯一问题是:pub(crate) 还是pub

对于#[macro_export]-ed 宏,您可以使用任何您想要的隐私;通常是pub

对于其他macro_rules! 宏,您不能超过pub(crate)


详细示例

对于非#[macro_export]ed 宏

mod foo 
    use super::example::my_macro;

    my_macro!(...); // OK


mod example 
    macro_rules! my_macro  ... 
    pub(crate) use my_macro;


example::my_macro!(...); // OK

对于#[macro_export]-ed 宏

在宏定义上应用#[macro_export] 使其在定义它的模块之后可见(以便与非#[macro_export]ed 宏的行为一致),但它也以绝对路径的方式将宏放在 crate 的根目录(宏被定义的地方)

这意味着在宏定义之后的 pub use macro_name; 或该 crate 的任何模块中的 pub use crate::macro_name; 都可以工作。

注意:为了使重新导出不与“在箱子根部导出”机制发生冲突,不能在箱子本身的根部进行。
pub mod example 
    #[macro_export] // macro nameable at `crate::my_macro`
    macro_rules! my_macro  ... 
    pub use my_macro; // macro nameable at `crate::example::my_macro`


pub mod foo 
    pub use crate::my_macro; // macro nameable at `crate::foo::my_macro`

使用pub / pub(crate) use macro_name; 时,请注意,鉴于命名空间在 Rust 中的工作方式,您可能还需要重新导出常量/函数或类型/模块。这也会导致全局可用的宏出现问题,例如#[test]#[allow(...)]#[warn(...)]等。

为了解决这些问题,请记住您可以在重新导出项目时对其进行重命名:

macro_rules! __test__  ... 
pub(crate) use __test__ as test; // OK

macro_rules! __warn__  ... 
pub(crate) use __warn__ as warn; // OK

此外,一些误报 lints 可能会触发:

来自 trigger-happy clippy 工具,当这个技巧以任何方式完成时;

来自rustc 本身,当这是在函数体内发生的macro_rules! 定义上完成时:https://github.com/rust-lang/rust/issues/78894

【讨论】:

我认为这是使用模块中的本地宏的唯一方法,该模块不直接位于定义宏的模块之上(例如,如果您在 a::b 中有一个宏,这是在没有#[macro_export] 的模块c 中使用它的唯一方法)。这是因为c 不能声明mod a::b 并且#[macro_use] 不适用于use 语句,例如use super::a::b 哦,哇,这个答案需要更多的支持。非常感谢! 重新导出的宏的行为与任何其他项目一样 - 基于此,我希望能够在(公共)子模块中定义宏一个板条箱,并使用完整路径从另一个板条箱引用它,即some_name::some_module::some_macro!。但是如果我定义macro_rules! some_macro ... 然后pub use some_macro; 编译器告诉我some_macro 是私有的。我可以使用pub(crate),如答案所示,但它是板条箱私有的,只能使用该板条箱的完整路径调用。有没有办法从另一个箱子通过完整路径调用它? @user4815162342 重新导出不能提供比项目的固有可见性更多的可见性。而非#[macro_export]-ed 宏的固有可见性确实最多为pub(crate)。因此,您需要 #[macro_export] 您的宏,尽管这也会使其出现在 crate 的根目录中。对于“也是箱子的根源”问题没有简单的解决方法,但是对于 doc hacks 来隐藏它,或者为此使用额外的外部帮助箱(例如在 konst_macro_rules 中的 crates.io/crates/konst/0.2.4/dependencies) 最后。这是我一直在寻找的答案。如果我在一个模块中定义一个宏,我希望它命名为该模块。这必须是 Rust 中最令人困惑且文档记录最差的特性,通常是宏。

以上是关于如何跨模块文件使用宏?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用来自另一个 Maven 模块的宏?

汇编语言多模块程序结构

汇编语言多模块程序结构

跨多个模块使用 Flask-pymongo

跨多个模块访问单例[重复]

159微信小程序跨页面跨组件同步全局状态跨页面跨组件通讯方案,使用自制广播模块实现