导入/导出

在 Rust 的 2015 和 2018 版本中,导入 macro_rules! 宏是不一样的。 仍然建议阅读这两部分,因为 2018 版使用的结构在 2015 版中做出了解释。

2015 版本

#[macro_use]

作用域 一章中介绍的 #[macro_use] 属性 适用于模块或者 external crates 。例如:

#[macro_use]
mod macros {
    macro_rules! X { () => { Y!(); } }
    macro_rules! Y { () => {} }
}

X!();

fn main() {}

#[macro_export]

可通过 #[macro_export] 将宏从当前crate导出。注意,这种方式 无视 所有可见性设定。

定义 lib 包 macs 如下:

mod macros {
    #[macro_export] macro_rules! X { () => { Y!(); } }
    #[macro_export] macro_rules! Y { () => {} }
}

// X! 和 Y! 并非在此处定义的,但它们 **的确** 被导出了(在此处可用)
// 即便 `macros` 模块是私有的

下面(在使用 macs lib 的 crate 中)的代码会正常工作:

X!(); // X 在当前 crate 中被定义
#[macro_use] extern crate macs; // 从 `macs` 中导入 X
X!(); // 这里的 X 是最新声明的 X,即 `macs` crate 中导入的 X

fn main() {}

正如 作用域 一章所说,#[macro_use] 作用于 extern crate 时, 会强制把导出的宏提到 crate 的顶层模块(根模块),所以这里无须使用 macs::macros 路径。

注意:只有在根模组中,才可将 #[macro_use] 用于 extern crate

在从 extern crate 导入宏时,可显式控制导入 哪些 宏。 从而利用这一特性来限制命名空间污染,或是覆盖某些特定的宏。就像这样:

// 只导入 `X!` 这一个宏
#[macro_use(X)] extern crate macs;

// X!(); // X! 已被定义,但 Y! 未被定义。X 与 Y 无关系。

macro_rules! Y { () => {} }

X!(); // X 和 Y 都被定义

fn main() {}

当导出宏时,常常出现的情况是,宏定义需要其引用所在 crate 内的非宏符号。 由于 crate 可能被重命名等,我们可以使用一个特殊的替换变量 $crate 。 它总将被扩展为宏定义所在的 crate 的绝对路径(比如 :: macs )。

如果你的编译器版本小于 1.30(即 2018 版之前),那么这招并不适用于宏。 也就是说,你没办法采用类似 $crate::Y! 的代码来引用自己 crate 里的定义的宏。 这表示结合 #[macro_use] 来选择性导入会无法保证某个名称的宏在另一个 crate 导入同名宏时依然可用。

推荐的做法是,在引用非宏名称时,总是采用绝对路径。 这样可以最大程度上避免冲突,包括跟标准库中名称的冲突。

2018 版本

2018 版本让使用 macro_rules! 宏更简单。 因为新版本设法让 Rust 中某些特殊的东西更像正常的 items 。 这意味着我们能以命名空间的方式正确导入和使用宏!

因此,不必使用 #[macro_use] 来导入 来自 extern crate 导出的宏 到全局命名空间, 现在我们这样做就好了:

use some_crate::some_macro;

fn main() {
    some_macro!("hello");
    // as well as
    some_crate::some_other_macro!("macro world");
}

可惜,这只适用于导入外部 crate 的宏; 如果你使用在自己 crate 定义的 macro_rules! 宏, 那么依然需要把 #[macro_use] 添加到宏所定义的模块上来引入模块里面的宏。 因而 作用域规则 就像之前谈论的那样生效。

$crate 前缀(元变量)在 2018 版中可适用于任何东西, 在 1.31 版之后,宏 和类似 item 的东西都能用 $crate 导入了。