如何跨模块文件使用宏?

我有两个单独的文件在同一个板条箱中的模块,其中板条箱启用了 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'”... 这是有意义的; 宏系统运行在模块系统之前。我该怎么做?

84131 次浏览

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


您需要在 macros.rs的顶部添加 #![macro_escape],并按照 宏指南中提到的使用 mod macros;包括它。

$ 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)

同一个箱子里的宏

新方法(生锈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

如果您想在同一个板条箱中使用宏,那么定义宏的模块需要 #[macro_use]属性。注意,宏只能使用 之后,它们已经被定义了!



横跨板条箱的宏

箱子 util

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

箱子 user

use util::foo;


foo!();

注意,使用这种方法,宏总是生活在板条箱的顶层!因此,即使 foo将在一个 mod bar {}user板条箱仍然必须写 use util::foo;没有 use util::bar::foo;。通过使用 pub use,您可以从板条箱的模块中导出宏(除了在根目录中导出之外)。

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

#![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?");
}

你的大脑有时候会像下面这样:

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关键字访问它们。

我在 Rust 1.44.1中有 遇到了同样的问题,这个解决方案适用于稍后的版本(已知适用于 Rust 1.7)。

假设你有一个新项目:

src/
main.rs
memory.rs
chunk.rs

水管中,您需要注释您正在从源代码导入宏,否则,它将不适合您。

#[macro_use]
mod memory;
mod chunk;


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

所以在 记忆中你可以定义宏,并且不需要注释:

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

最后,您可以在 大块的中使用它,并且您不需要在这里包含宏,因为它是在 main.rs 中完成的:

grow_capacity!(8);

投票结果给我带来了困惑,对于 这个文件的例子来说,它也是有帮助的。


注意: 这个解决方案是可行的,但是请注意,在注释 在 ABC1中声明 mod的命令很重要中突出显示的@ineiti,在宏 mod 声明之后声明的所有 mod尝试调用宏都将失败。

截至 1.32.0(2018年版)的替代方法

请注意,虽然 来自@lukas-kalbertodt 的说明仍然是最新的并且工作得很好,但是必须记住宏的特殊名称空间规则的想法可能会让一些人感到厌烦。

在2018年版及以后的版本中,自从《锈》的 1.32.0版本以来,还有另外一种方法也同样有效,它的好处是使它更容易教(例如:。,它使得 #[macro_use]过时)。关键思想如下:

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

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

  • 它也可以在本地 used/进入作用域,以便以不限定的方式引用它。

例子

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

就是这样,很简单,对吧?


如果你不害怕信息超载的话,你可以继续读下去)我会详细说明为什么,怎么做,以及什么时候做这件事。

更详细的解释

为了重新导出(pub(...) use ...)一个宏,我们需要参考它!这就是原始答案中的规则的有用之处: 宏总是可以在宏定义发生的模块中命名,但只能在定义之后的 < em > 命名

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;速记,它仍然可以工作。

剩下的唯一问题是: ABC0或 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]宏的行为保持一致) ,即 但是它也将宏放在板条箱的根部(定义宏的地方) ,以一种绝对路径的方式

    这意味着在宏定义之后的 pub use macro_name;,或者在箱子的任何模块中的 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

此外,一些假阳性的线条可能会激发:

  • 从触发高兴的 clippy工具,当这个把戏是做在任何方式;

  • rustc本身,当这是在一个发生在函数体内的 macro_rules!定义上完成的时候: < a href = “ https://github.com/ust-lang/ust/issues/78894”rel = “ norefrer”> https://github.com/rust-lang/rust/issues/78894