如何在使用Cargo/Rust的模块中包含来自相同目录的文件?

62

我有一个Cargo项目,由同一目录下的三个文件main.rsmod1.rsmod2.rs组成。

我想要像在main.rs中导入mod1.rs中的函数一样,从mod2.rs中导入函数到mod1.rs中。
我已经阅读了所需的文件结构,但是我不理解 - 将所有导入的文件命名为mod将会导致编辑器中的轻微混淆,而且这只是让项目层次更加复杂。

是否有一种可独立于目录结构导入/包含文件的方式,就像在Python或C++中一样?

main.rs:

mod mod1; // Works

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}

mod1.rs:

mod mod2; // Fails

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}

mod2.rs:

pub fn mod2fn() {
    println!("2");
}

建筑物产生的结果:

error: cannot declare a new module at this location
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^
  |
note: maybe move this module `src` to its own directory via `src/mod.rs`
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^
note: ... or maybe `use` the module `mod2` instead of possibly redeclaring it
 --> src\mod1.rs:1:5
  |
1 | mod mod2;
  |     ^^^^

我无法使用它,因为它在任何地方都不存在作为一个模块,而且我不想修改目录结构。

3个回答

102

所有顶级模块声明都应该放在 main.rs 中,就像这样:

mod mod1;
mod mod2;

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}
您可以在mod1中使用crate::mod2
use crate::mod2;

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}

如果你还没有阅读过,我建议阅读Rust新版本书中关于模块的章节 - 对于刚接触这门语言的人来说,它们可能会有点混淆。


6
我阅读了那一章,但不理解其中描述的内容如何实现。 - Neo
1
@Neo:没问题!一旦你对模块系统有了感觉,它就很有意义,但肯定有一定的学习曲线 - 目前正在进行简化工作 - Joe Clay
@Neo:我也推荐阅读这篇关于如何理解模块系统的博客文章:http://manishearth.github.io/blog/2017/05/14/mentally-modelling-modules - Joe Clay

8

每个文件都是一个模块,不能在不创建新嵌套模块的情况下导入其他文件。

a. 在模块索引文件中定义模块

建议按照 @giuliano-oliveira's answer 的方式进行操作。

src/lib.rs / src/main.rs / src/foo/mod.rs 中添加 pub mod mod1; pub mod mod2;

b. 使用 #[path]

main.rs

#[path = "./mod2.rs"]
mod mod2;

fn run() { mod2::mod2fn() }

为什么会有这个问题?

对于新手 Rust 开发者来说,这是一个常见的陷阱,这也是可以理解的。

混淆的原因在于同一文件夹中的文件使用 mod X 的行为不一致。你可以在 lib.rs 中使用 mod X,看起来它会导入与其相邻的文件,但你不能在 mod1.rsmod2.rs 中这样做。

每个文件的代码属于一个模块。文件模块的完整路径(例如 foo::bar::baz)而不是文件位置决定了如何解析 mod X。你可以将其想象为每个模块都有一个固定的精神家园,但它可能在层次结构中定义更高级别的成员(例如 src/lib.rs 可能包含:mod foo { mod bar { pub fn hello() {} } },但这时你不能在 lib.rs 中单独使用 mod foo; )。

main.rs 中,你处于顶级模块 crate

mod mod1; 创建一个新的模块 mod1,并将 ./mod1.ts 的内容添加到该模块中。

因此,./mod1.rs 中的所有代码都在 crate::mod1 模块内。

当您在 ./mod1.rs 中调用 use mod2 时,它会看到它位于 crate::mod1 内,其精神家园目录为 src/mod1,并寻找以下内容之一:

  • src/mod1/mod2.rs
  • src/mod1/mod2/mod.rs
复杂性来自于允许模块既可以是目录也可以是文件,而不是强制每个模块都是自己的目录(可能是为了避免Java的文件夹结构)这将消除歧义。
需要记住的关键是,lib.rsmod.rs是与目录中其他文件不同的特殊文件。
它们将始终在由父文件夹路径描述的模块中(例如src/foo/bar/mod.rs=foo::bar),而所有其他文件都属于它们自己的模块(src/foo/bar/baz.rs=foo::bar::baz)。
Rust对此有一些看法。
不再推荐使用mod.rs,这很好,因为它与其兄弟姐妹有着特殊的行为。但是lib.rsmain.rs仍然是特殊的。
如果您想将测试与代码放在一起(foo.rs + foo_test.rs),建议不要这样做。我不喜欢这样,所以我使用上面的 path 方法,因为我认为对于测试来说这是可以接受的,因为它们没有被导出到任何地方。在上面的模块中声明测试感觉不对,我也不喜欢使用 foo/test.rs

3
"Use #[path]" 作为一个大而粗的标题,会给浏览者留下错误的印象,尤其当你的回答内容与此相反时。我个人会避免提及它。 - kmdreko
OP 的最后一个问题是:“是否有一种方法可以独立于目录结构导入/包含文件,就像在 Python 或 C++ 中一样?”实际上,使用路径是针对这个特定问题的正确答案。因此,我认为它比被接受的答案更正确。无论如何,值得一提。 - vaughan
如果你的目标是“技术上正确”,我仍然认为你的答案不足。它没有解释#[path = ...]如何工作以及为什么不建议使用它。解释main.rs/lib.rs/mod.rs是特殊的,并引入略微不同的结构(即foo.rs而不是foo/mod.rs)会使这个答案变得更好。我只是希望你能重新措辞一些内容。我见过太多新手使用#[path = ...]而不是传统的导入方式,他们来到SO时不可避免地感到困惑,因为他们的代码无法正常工作。 - kmdreko
好的,我稍微改了一下措辞。 - vaughan
我不明白#[path]方法有什么问题。这是我唯一能让事情正常工作的方法。 - PaulCommentary
我喜欢在Rust项目中不需要太多的mod.rs文件的想法,这也是我喜欢#[path]的另一个原因。不过必须承认,@giuliano-oliveira的方法让我感到困惑。 - PaulCommentary

6

如果您不想在主文件中(例如:main.rs)使用某些模块内的公共成员(在此示例中为mod2),可以将您的mod语句分离出来。

按照以下方式构建你的src目录结构:

main.rs
my_module:
  mod.rs
  mod1.rs
  mod2.rs

那么你可以这样做: mod my_module,然后使用my_module::mod1
main.rs:
mod my_module;
use my_module::mod1;

fn main() {
    println!("Hello, world!");
    mod1::mod1fn();
}

my_module/mod.rs

pub mod mod1;
pub mod mod2;

my_module/mod1.rs

use super::mod2;

pub fn mod1fn() {
    println!("1");
    mod2::mod2fn();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接