如何跨模块文件使用宏?

use*_*ser 59 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?
Run Code Online (Sandbox Code Playgroud)

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

Luk*_*odt 90

同一个箱子里的宏

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

bar!();    // works
Run Code Online (Sandbox Code Playgroud)

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

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

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

#[macro_use]
mod foo {
    macro_rules! bar {
        () => ()
    }
}
Run Code Online (Sandbox Code Playgroud)

横跨板条箱的宏

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

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


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

foo!();
Run Code Online (Sandbox Code Playgroud)

注意:宏总是位于箱子的顶层; 因此,即使foo是在里面mod bar {},user箱子仍然必须写,use util::foo;不是 use util::bar::foo;.

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


有关更多信息,请参阅The Rust Programming Language.

  • "宏只能在定义后才能使用." - 这很关键,因为即使你已经完成了正确提到的所有其他事情,你也会遇到这个错误.例如,如果你有模块`macros`和`foo`(使用来自`macros`的宏),并且你在lib.rs或main.rs中按字母顺序列出它们,foo将在宏和宏之前加载代码不会编译. (18认同)
  • 另请注意,为了在内部使用宏,`#[macro_use]` 属性应该位于每个模块和父模块等上,直到它到达您需要使用它的位置。 (7认同)
  • ^专业提示 - 这完全得到了我 (3认同)
  • 这个答案对我不起作用。声明宏的模块有“#[macro_use]”,并且它首先在 lib.rs 中声明 - 仍然不起作用。@Ten 的回答很有帮助,我将 `#[macro_use]` 添加到 lib.rs 的顶部 - 然后它就起作用了。但我仍然不确定最佳实践是什么,因为我在[此处](https://www.reddit.com/r/rust/comments/2lc84e/importing_macros_from_another_module/)读到“你不从其他模块导入宏”模块;您从定义模块中导出宏” (2认同)
  • 我总是忘记 Rust 的宏是如何与模块一起工作的。这是一个糟糕的系统,希望有一天会有一个更好的系统。 (2认同)

Dog*_*ert 15

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


您需要#![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
Run Code Online (Sandbox Code Playgroud)

备查,

$ rustc -v
rustc 0.13.0-dev (2790505c1 2014-11-03 14:17:26 +0000)
Run Code Online (Sandbox Code Playgroud)

  • BTW,这里不需要`#[macro_export]`属性.仅在将宏导出到外部包装箱用户时才需要它.如果宏只在包中使用,则不需要`#[macro_export]`. (3认同)
  • (这个答案现在已经过时了;我接受了Lukas给出的最新答案) (2认同)

Luk*_*pin 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?");
}
Run Code Online (Sandbox Code Playgroud)

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

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

fn main() {
    test!();
    best!();
    toad::a_thing();
}
Run Code Online (Sandbox Code Playgroud)

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

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

pub fn a_thing() {
  test!();

  node::fun_times();
}
Run Code Online (Sandbox Code Playgroud)

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


Dan*_*H-M 13

替代方法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™
Run Code Online (Sandbox Code Playgroud)

就是这样。很简单吧?


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

更详细的解释

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

macro_rules! my_macro { ... }
my_macro!(...); // OK
Run Code Online (Sandbox Code Playgroud)
// Not OK
my_macro!(...); /* Error, no `my_macro` in scope! */
macro_rules! my_macro { ... }
Run Code Online (Sandbox Code Playgroud)

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

剩下的唯一问题是: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
    
    Run Code Online (Sandbox Code Playgroud)
  • 对于#[macro_export]-ed 宏

    应用#[macro_export]宏定义使其在定义它的模块之后可见(以便与未#[macro_export]编辑的宏的行为一致),但它也将宏放在 crate 的根部(宏定义的位置) ),以绝对路径方式

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

    • 注意:为了使重新导出不与“在箱子的根部导出”机制发生冲突,不能在箱子本身的根部进行。
    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`
    }
    
    Run Code Online (Sandbox Code Playgroud)

使用 时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
Run Code Online (Sandbox Code Playgroud)

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

  • 最后。这就是我一直在寻找的答案。如果我在模块中定义宏,我希望将其命名空间到该模块。一般来说,这一定是 rust 宏中最令人困惑和记录最差的功能。 (2认同)

knh*_*190 7

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

假设您有一个新项目:

src/
    main.rs
    memory.rs
    chunk.rs
Run Code Online (Sandbox Code Playgroud)

main.rs 中,您需要注释您是从源导入宏,否则,它将不适合您。

src/
    main.rs
    memory.rs
    chunk.rs
Run Code Online (Sandbox Code Playgroud)

因此,在memory.rs 中,您可以定义宏,并且不需要注释:

#[macro_use]
mod memory;
mod chunk;

fn main() {
    println!("Hello, world!");
}
Run Code Online (Sandbox Code Playgroud)

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

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

upvoted答案引起了混乱对我来说,与这个文档的例子,这将有助于太。

  • @Shepmaster,被投票的答案在同一位置有宏的定义和导入语句,所以它引起了混乱(对我来说)。我在定义中使用了“#[macro_use]”。编译器并没有说它放错了地方。 (3认同)
  • 还要确保“main.rs”中“mod”的顺序正确。如果你有`mod chunk; mod memory;`,那么`memory.rs`中的宏调用将会失败。 (2认同)