如何跨模块文件使用宏?

时间:2014-11-04 09:07:24

标签: module rust rust-macros

我在同一个包中的两个单独文件中有两个模块,其中包已启用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?

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

5 个答案:

答案 0 :(得分:64)

同一个箱子里的宏

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

bar!();    // works

如果要在同一个包中使用宏,则定义宏的模块需要属性#[macro_use]

宏只能在 定义后使用。这意味着这不起作用:

bar!();  // ERROR: cannot find macro `bar!` in this scope

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

横跨板条箱的宏

要使用其他包中的macro_rules!宏,宏本身需要属性#[macro_export]。然后导入包可以通过use crate_name::macro_name;导入宏。

// --- Crate `util` ---
#[macro_export]
macro_rules! foo {
    () => ()
}


// --- Crate `user` ---
use util::foo;

foo!();

注意:宏总是位于包的顶层;因此即使foo位于mod bar {}内,user包仍需要写use util::foo; use util::bar::foo;

(在Rust 2018之前,您必须通过将属性#[macro_use]添加到extern crate util;语句来从其他包中导入宏。这将从util导入所有宏。或者,{{ 1}}可用于仅导入宏#[macro_use(cat, dog)]cat。此语法不再是必需的。)

The Rust Programming Language中提供了更多信息。

答案 1 :(得分:15)

从Rust 1.1.0-stable开始,这个答案已经过时了。


您需要在#![macro_escape]的顶部添加macros.rs,并使用Macros Guide中提到的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}}

答案 2 :(得分:7)

替代方法 1.32.0(2018 年版)

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

事实证明,在 2018 年及以后的版本中,从 Rust 的 1.32.0 版本开始,还有另一种方法也行得通,而且它的好处是,恕我直言,它使教学更容易(< em>eg,它使 #[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;
    }
    
  • 我们也可以:

    struct Foo {}
    
    fn main() {
        let _: A;
    }
    
    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]编辑的宏

    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] 的宏

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

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

    • 注意:为了使重新导出不与“在 crate 的根部导出”机制发生冲突,不能在 crate 本身的根部进行。
    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

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

答案 3 :(得分:3)

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

假设您有一个新项目,如下:

src/
    main.rs
    memory.rs
    chunk.rs

main.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也很有帮助。

答案 4 :(得分:1)

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