如何跨模块文件使用宏?
Posted
技术标签:
【中文标题】如何跨模块文件使用宏?【英文标题】:How do I use a macro across module files? 【发布时间】:2014-12-31 02:42:27 【问题描述】:我在同一个 crate 中的不同文件中有两个模块,其中 crate 启用了 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'
”...这是有道理的;宏系统在模块系统之前运行。我该如何解决这个问题?
【问题讨论】:
你不应该使用module::my_macro!()?
nope (not afaik) - 据报道模块前缀被忽略(根据编译器消息)。
【参考方案1】:
这个答案在 Rust 1.1.0-stable 中已经过时了。
您需要在macros.rs
的顶部添加#![macro_escape]
并使用mod macros;
将其包含在Macros Guide 中。
$ 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)
【讨论】:
我完全错过了这个属性。谢谢! 顺便说一句,#[macro_export]
属性在这里是不必要的。仅当宏应导出到外部 crate 用户时才需要。如果宏只在 crate 内部使用,则不需要#[macro_export]
。
非常感谢您的回答。我只想补充一点,如果您的something.rs
文件使用其他模块,例如mod foobar;
,并且此foobar
模块使用来自macro.rs
的宏,那么您必须将mod macro;
放在前面 mod foobar;
用于编译程序。小事,但这不是一个明显的 IMO。
(n.b. 这个答案现在已经过时了;我已经接受了 Lukas 给出的最新答案)【参考方案2】:
同一个 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
会在mod bar
中,user
板条箱仍然必须写use util::foo;
和不 use util::bar::foo;
。通过使用pub use
,您可以从 crate 的模块中导出宏(除了在根目录中导出)。
在 Rust 2018 之前,您必须通过将属性 #[macro_use]
添加到 extern crate util;
语句来从其他 crate 导入宏。这将从util
导入所有宏。这种语法应该不再需要了。
【讨论】:
"宏只能在定义后才能使用。" - 这是关键,因为即使您正确完成了所有其他提到的事情,您也可能会遇到该错误。例如,如果您有模块macros
和 foo
(使用来自 macros
的宏),并且您在 lib.rs 或 main.rs 中按字母顺序列出它们,则 foo 将在宏之前加载,并且代码无法编译。
^ 专业提示 - 这完全吸引了我
还要注意,为了在内部使用宏,#[macro_use]
属性应该在每个模块和父模块等上。直到它到达你需要使用它的地步。
这个答案对我不起作用。声明宏的模块有 #[macro_use]
并且它首先在 lib.rs 中声明 - 仍然没有工作。 @Ten 的回答有所帮助,我在 lib.rs 的顶部添加了 #[macro_use]
- 然后它起作用了。但我仍然不确定最佳实践是什么,因为我读过 here 说“你不从其他模块导入宏;你从定义模块导出宏”
我总是忘记 Rust 的宏是如何与模块一起工作的。这是一个糟糕的系统,希望有一天会有更好的系统。【参考方案3】:
将#![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();
请注意,一旦使用 mod
将文件拉入 main.rs,您的其余文件就可以通过 use
关键字访问它们。
【讨论】:
我添加了更多说明。从 rustc 1.22.1 开始,这确实有效。 你确定吗?这个 #![macro_use](不是 #[macro_use])记录在哪里?我找不到它。它在这里不起作用。 这在我发布时有效,Rust 的包含系统如此糟糕,完全有可能不再有效。 @Markus 注意#![macro_use]
语句是INSIDE宏模块,而不是外部。 #![...]
语法对应于将属性应用于其包含范围,例如#![feature(...)]
(如果写成 #[feature(...)]
显然这没有意义;它在语义上要求编译器在 crate 中的特定项目上启用某些功能,而不是在整个根 crate 上)。因此,正如@LukeDupin 所说,模块系统一团糟,尽管原因可能与乍一看不同。
我确实希望这个答案提到了这个结构不是完全地道的(除此之外,我喜欢这个答案)。尽管它具有(非)惯用性,但它很有趣,因为将它放在惯用形式旁边会让人很痛苦地看到,宏与模块系统的交互方式与通常的结构不同。或者它至少会散发出强烈的气味(正如@Markus 对它的抱怨所证明的那样)。【参考方案4】:
我在 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 也会有帮助。
注意:此解决方案确实有效,但请注意 @ineiti 在 cmets 中突出显示,您在 main.rs/lib.rs
中声明 mod
s 的顺序很重要 , 在宏 mod 声明之后声明的所有 mod
s 尝试调用宏都会失败。
【讨论】:
@Shepmaster 赞成的答案在同一个地方定义了宏和导入语句,所以它引起了混乱(对我来说)。我在定义中使用了#[macro_use]
。编译器不会说它放错地方了。
感谢您的回答!我也被接受的答案弄糊涂了,直到我读了你的解释才弄明白。
@Shepmaster 在您链接到的部分中没有提及宏是如何工作的。您的意思是链接到本书的其他部分吗?
@detly 不,因为我的评论指出的内容比宏更广泛。这个回答者似乎很困惑mod ...
和mod some_file
是同一个东西并且都创建了一个模块。接受的答案已经显示了#[macro_use]
的用法,因此 this 答案并没有真正提供任何新内容。
还要确保mod
在main.rs
中的顺序正确。如果你有mod chunk; mod memory;
,那么memory.rs
中的宏调用将会失败。【参考方案5】:
1.32.0
(2018 版)的替代方法
请注意,虽然 the instructions from @lukas-kalbertodt 仍然是最新的并且运行良好,但必须记住宏的特殊命名空间规则的想法可能会让某些人感到烦恼。
编辑:原来他们的答案是updated to include my suggestion,没有任何信用提及?在 2018 版及以后的版本中,自 Rust 的 1.32.0
版本以来,还有另一种方法也有效,并且恕我直言,它的好处是让教学更容易(例如,它使#[macro_use]
过时)。关键思想如下:
重新导出的宏的行为与任何其他项(函数、类型、常量、等)一样:它在重新导出发生的模块内命名空间。
然后可以使用完全限定的路径来引用它。
它也可以在本地use
d / 引入范围,以便以非限定方式引用它。
示例
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;
或该 crate 的任何模块中的 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
此外,一些误报 lints 可能会触发:
来自 trigger-happy clippy
工具,当这个技巧以任何方式完成时;
来自rustc
本身,当这是在函数体内发生的macro_rules!
定义上完成时:https://github.com/rust-lang/rust/issues/78894
【讨论】:
我认为这是使用模块中的本地宏的唯一方法,该模块不直接位于定义宏的模块之上(例如,如果您在a::b
中有一个宏,这是在没有#[macro_export]
的模块c
中使用它的唯一方法)。这是因为c
不能声明mod a::b
并且#[macro_use]
不适用于use
语句,例如use super::a::b
。
哦,哇,这个答案需要更多的支持。非常感谢!
重新导出的宏的行为与任何其他项目一样 - 基于此,我希望能够在(公共)子模块中定义宏一个板条箱,并使用完整路径从另一个板条箱引用它,即some_name::some_module::some_macro!
。但是如果我定义macro_rules! some_macro ...
然后pub use some_macro;
编译器告诉我some_macro
是私有的。我可以使用pub(crate)
,如答案所示,但它是板条箱私有的,只能使用该板条箱的完整路径调用。有没有办法从另一个箱子通过完整路径调用它?
@user4815162342 重新导出不能提供比项目的固有可见性更多的可见性。而非#[macro_export]
-ed 宏的固有可见性确实最多为pub(crate)
。因此,您需要 #[macro_export]
您的宏,尽管这也会使其出现在 crate 的根目录中。对于“也是箱子的根源”问题没有简单的解决方法,但是对于 doc hacks 来隐藏它,或者为此使用额外的外部帮助箱(例如在 konst_macro_rules
中的 crates.io/crates/konst/0.2.4/dependencies)
最后。这是我一直在寻找的答案。如果我在一个模块中定义一个宏,我希望它命名为该模块。这必须是 Rust 中最令人困惑且文档记录最差的特性,通常是宏。以上是关于如何跨模块文件使用宏?的主要内容,如果未能解决你的问题,请参考以下文章