143

我在同一个板条箱中的不同文件中有两个模块,板条箱已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'”......这是有道理的;宏系统在模块系统之前运行。我该如何解决这个问题?

4

5 回答 5

198

同一个 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在 amod bar {}中,user板条箱仍然必须写入use util::foo;不是 use util::bar::foo;。通过使用pub use,您可以从 crate 的模块中导出宏(除了在根目录中导出)。

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

于 2015-07-31T14:47:17.047 回答
31

1.32.0(2018 版)的替代方法

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

在 2018 版及以后的版本1.32.0中,自 Rust 版本以来,还有另一种方法也有效,恕我直言,它的好处是让教学更容易(例如,它#[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;在宏定义之后的右边,或者pub use crate::macro_name;在那个 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

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

于 2021-04-17T16:17:52.380 回答
25

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


您需要按照Macros Guide#![macro_escape]中的说明在顶部添加macros.rs并包含它。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)
于 2014-11-04T09:18:00.213 回答
13

添加#![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();
}

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

于 2018-01-16T07:23:52.977 回答
8

我在 Rust 1.44.1 中遇到了同样的问题,这个解决方案适用于更高版本(已知适用于 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);

赞成的答案让我感到困惑,以这个文档为例,它也会有所帮助。


注意:此解决方案确实有效,但请注意注释中突出显示的@ineiti,您在事件中声明s的顺序modmain.rs/lib.rsmod,在宏 mod 声明之后声明的所有s 尝试调用宏将失败。

于 2020-08-03T17:53:30.273 回答