跨多个文件拆分模块
Posted
技术标签:
【中文标题】跨多个文件拆分模块【英文标题】:Split a module across several files 【发布时间】:2014-05-01 01:45:00 【问题描述】:我想要一个包含多个结构的模块,每个都在自己的文件中。以Math
模块为例:
Math/
Vector.rs
Matrix.rs
Complex.rs
我希望每个结构都在同一个模块中,我将从我的主文件中使用,如下所示:
use Math::Vector;
fn main()
// ...
然而,Rust 的模块系统(一开始有点混乱)并没有提供明显的方法来做到这一点。它似乎只允许您将整个模块放在一个文件中。这不土气吗?如果没有,我该怎么做?
【问题讨论】:
我解释说“我想要一个包含多个结构的模块,每个结构都在它自己的文件中。”表示您希望每个结构定义都在其自己的文件中。 这不会被认为是质朴的,尽管模块系统当然允许这样的结构。模块路径通常最好直接对应于文件系统路径,例如structfoo::bar::Baz
应该在 foo/bar.rs
或 foo/bar/mod.rs
中定义。
【参考方案1】:
Rust 的模块系统实际上非常灵活,可以让您公开所需的任何类型的结构,同时隐藏代码在文件中的结构。
我认为这里的关键是使用pub use
,这将允许您从其他模块重新导出标识符。在 Rust 的 std::io
crate 中有这样的先例,其中子模块中的某些类型是 re-exported for use in std::io
。
编辑(2019-08-25):答案的以下部分是很久以前写的。它解释了如何单独使用
rustc
设置这样的模块结构。今天,人们通常会在大多数用例中使用 Cargo。虽然以下内容仍然有效,但其中的某些部分(例如#![crate_type = ...]
)可能看起来很奇怪。这不是推荐的解决方案。
为了适应你的例子,我们可以从这个目录结构开始:
src/
lib.rs
vector.rs
main.rs
这是你的main.rs
:
extern crate math;
use math::vector;
fn main()
println!(":?", vector::VectorA::new());
println!(":?", vector::VectorB::new());
还有你的src/lib.rs
:
#[crate_id = "math"];
#[crate_type = "lib"];
pub mod vector; // exports the module defined in vector.rs
最后,src/vector.rs
:
// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;
mod vector_b; // private sub-module defined in vector_b.rs
mod vector_a // private sub-module defined in place
#[derive(Debug)]
pub struct VectorA
xs: Vec<i64>,
impl VectorA
pub fn new() -> VectorA
VectorA xs: vec![]
这就是魔法发生的地方。我们定义了一个子模块math::vector::vector_a
,它实现了一种特殊的向量。但是我们不希望您图书馆的客户关心有一个vector_a
子模块。相反,我们希望在 math::vector
模块中提供它。这是通过pub use self::vector_a::VectorA
完成的,它在当前模块中重新导出vector_a::VectorA
标识符。
但是您询问了如何执行此操作,以便可以将您的特殊向量实现放在不同的文件中。这就是mod vector_b;
行的作用。它指示 Rust 编译器为该模块的实现查找 vector_b.rs
文件。果然,这是我们的src/vector_b.rs
文件:
#[derive(Debug)]
pub struct VectorB
xs: Vec<i64>,
impl VectorB
pub fn new() -> VectorB
VectorB xs: vec![]
从客户端的角度来看,VectorA
和 VectorB
在两个不同文件中的两个不同模块中定义的事实是完全不透明的。
如果你和main.rs
在同一个目录下,你应该可以运行它:
rustc src/lib.rs
rustc -L . main.rs
./main
总的来说,Rust 书中的"Crates and Modules" chapter 还是不错的。例子很多。
最后,Rust 编译器还会自动为您查找子目录。例如,上面的代码将在此目录结构下保持不变:
src/
lib.rs
vector/
mod.rs
vector_b.rs
main.rs
编译和运行的命令也保持不变。
【讨论】:
我相信你误解了我所说的“矢量”的意思。我说的是the mathematical quantity 中的向量,而不是数据结构。另外,我没有运行最新版本的 rust,因为在 windows 上构建有点痛苦。 +1 不是我所需要的,但为我指明了正确的方向。 @EpicPineapple 确实!并且可以使用 Vec 来表示这样的向量。 (当然,对于更大的 N。) @EpicPineapple 你能解释一下我的答案遗漏了什么以便我更新吗?除了使用math::Vec2
而不是math::vector::Vec2
之外,我很难看到你的答案和我的答案之间的区别。 (即,相同的概念,但更深一个模块。)
我在您的问题中没有看到该标准。据我所知,我已经回答了提出的问题。 (这实际上是在问如何将模块与文件分离。)很抱歉它不适用于 Rust 0.9,但这与使用不稳定语言有关。【参考方案2】:
Rust 模块规则是:
-
源文件只是它自己的模块(特殊文件 main.rs、lib.rs 和 mod.rs 除外)。
目录只是一个模块路径组件。
文件 mod.rs 只是目录的模块。
目录math中的文件matrix.rs1就是模块math::matrix
。这很容易。您在文件系统上看到的内容也可以在源代码中找到。这是文件路径和模块路径的一一对应2.
因此您可以使用use math::matrix::Matrix
导入结构Matrix
,因为该结构位于matrix.rs 目录的math 目录中。不开心?你更喜欢use math::Matrix;
,不是吗?这是可能的。在 math/mod.rs 中重新导出标识符 math::matrix::Matrix
:
pub use self::math::Matrix;
还有另一个步骤可以让这个工作正常进行。 Rust 需要一个模块声明来加载模块。在 main.rs 中添加 mod math;
。如果你不这样做,你会在导入时从编译器收到一条错误消息:
error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?
这里的提示具有误导性。不需要额外的 crate,当然你真的打算编写一个单独的库。
在 main.rs 的顶部添加这个:
mod math;
pub use math::Matrix;
子模块vector
、matrix
和complex
也需要模块声明,因为math
需要加载它们以重新导出它们。仅当您已加载标识符的模块时,标识符的重新导出才有效。这意味着,要重新导出标识符math::matrix::Matrix
,您需要编写mod matrix;
。您可以在 math/mod.rs 中执行此操作。因此创建具有此内容的文件:
mod vector;
pub use self::vector::Vector;
mod matrix;
pub use self::matrix::Matrix;
mod complex;
pub use self::complex::Complex;
啊,你已经完成了。
1Rust 中源文件名通常以小写字母开头。这就是我使用 matrix.rs 而不是 Matrix.rs 的原因。
2Java 不同。你也用package
声明路径。这是多余的。从文件系统中的源文件位置,该路径已经很明显了。为什么要在文件顶部的声明中重复此信息?当然,有时快速查看源代码比找出文件的文件系统位置更容易。我能理解那些说它不那么令人困惑的人。
【讨论】:
顶部的 tl;dr 应该在 rust 文档中!【参考方案3】:Rusts 纯粹主义者可能会称我为异端并讨厌这个解决方案,但这更简单:只需在自己的文件中执行每件事,然后在 mod.rs 中使用“include!”宏:
include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");
这样您就不会添加嵌套模块,并避免复杂的导出和重写规则。 简单、有效、不费吹灰之力。
【讨论】:
你刚刚扔掉了命名空间。以与另一个文件无关的方式更改一个文件现在可以破坏其他文件。您对“使用”的使用变得泄漏(即一切都像use super::*
)。您不能从其他文件中隐藏代码(这对于不安全使用安全抽象很重要)
是的,但这正是我在这种情况下想要的:有几个文件作为命名空间的一个。我并不是在每种情况下都提倡这种做法,但如果您不想处理“每个文件一个模块”方法,无论出于何种原因,这是一个有用的解决方法。
这太棒了,我的模块的一部分只是内部的,但自包含,这就成功了。我也会尝试让合适的模块解决方案发挥作用,但这并不容易。
我不在乎被称为异端,你的解决方案很方便!【参考方案4】:
好吧,与我的编译器斗争了一段时间,终于让它工作了(感谢 BurntSushi 指出pub use
。
main.rs:
use math::Vec2;
mod math;
fn main()
let a = Vec2x: 10.0, y: 10.0;
let b = Vec2x: 20.0, y: 20.0;
数学/mod.rs:
pub use self::vector::Vec2;
mod vector;
数学/向量.rs
use std::num::sqrt;
pub struct Vec2
x: f64,
y: f64
impl Vec2
pub fn len(&self) -> f64
sqrt(self.x * self.x + self.y * self.y)
// other methods...
可以以相同的方式添加其他结构。注意:使用 0.9 编译,而不是 master。
【讨论】:
请注意,您在main.rs
中使用mod math;
会将您的main
程序与您的库结合起来。如果您希望 math
模块是独立的,则需要单独编译它并使用 extern crate math
链接到它(如我的回答所示)。在 Rust 0.9 中,语法可能改为 extern mod math
。
将 BurntSushi5 的答案标记为正确答案真的很公平。
@NSAddict 否。要从文件中分离模块,您不需要创建单独的 crate。它被过度设计了。
为什么这不是投票最多的答案?该问题询问如何将项目分解为几个文件,这与此答案所示的一样简单,而不是如何将其拆分为板条箱,这更难,这是@BurntSushi5 回答的(也许问题已被编辑?)。 ..
@BurntSushi5 的答案应该是公认的答案。这在社交上很尴尬,甚至可能意味着提出一个问题,得到一个非常好的答案,然后将其总结为一个单独的答案并将您的总结标记为已接受的答案。【参考方案5】:
我想在这里补充一下当 Rust 文件深度嵌套时如何包含它们。我有以下结构:
|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs
如何从main.rs
访问sink.rs
或toilet.rs
?
正如其他人所提到的,Rust 对文件一无所知。相反,它将一切都视为模块和子模块。要访问浴室目录中的文件,您需要将它们导出或将它们存储到顶部。您可以通过在文件中指定带有您要访问的目录和pub mod filename_inside_the_dir_without_rs_ext
的文件名来执行此操作。
示例。
// sink.rs
pub fn run()
println!("Wash my hands for 20 secs!");
// toilet.rs
pub fn run()
println!("Ahhh... This is sooo relaxing.")
在home
目录中创建一个名为bathroom.rs
的文件:
导出文件名:
// bathroom.rs
pub mod sink;
pub mod toilet;
在main.rs
旁边创建一个名为home.rs
的文件
pub mod
浴室.rs文件
// home.rs
pub mod bathroom;
main.rs
内
// main.rs
// Note: If you mod something, you just specify the
// topmost module, in this case, home.
mod home;
fn main()
home::bathroom::sink::run();
use
语句也可以使用:
// main.rs
// Note: If you mod something, you just specify the
// topmost module, in this case, home.
use home::bathroom::sink, toilet;
fn main()
sink::run();
sink::toilet();
在子模块中包含其他同级模块(文件)
如果您想使用toilet.rs
中的sink.rs
,您可以通过指定self
或super
关键字来调用该模块。
// inside toilet.rs
use self::sink;
pub fn run()
sink::run();
println!("Ahhh... This is sooo relaxing.")
最终目录结构
你会得到这样的结果:
|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs
上述结构仅适用于 Rust 2018 及以后的版本。以下目录结构也适用于 2018 年,但它是 2015 年过去的工作方式。
|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs
其中home/mod.rs
与./home.rs
相同,home/bathroom/mod.rs
与home/bathroom.rs
相同。 Rust 做出了这个改变,因为如果你包含一个与目录同名的文件,编译器会感到困惑。 2018 版本(第一个显示的版本)修复了该结构。
查看this repo了解更多信息,查看YouTube video了解整体说明。
最后一件事...避免使用连字符!请改用snake_case
。
重要提示
您必须将所有文件放到顶部,即使***文件不需要深层文件。
这意味着,要让sink.rs
发现toilet.rs
,您需要使用上述方法一直到main.rs
!
换句话说,在toilet.rs
中执行pub mod sink;
或use self::sink;
将行不通,除非您将它们一直暴露到main.rs
!
因此,请始终记住将文件放到顶部!
【讨论】:
另一件事,请记住将您的文件和文件夹放在src
文件夹中。【参考方案6】:
导出模块的一个更沙沙的方法,我从Github 中挑选出来的。
mod foo
//! inner docstring comment 1
//! inner docstring comment 2
mod a;
mod b;
pub use a::*;
pub use b::*;
【讨论】:
作为一个 Rust 新手,这对我来说已经足够好了。谢谢!以上是关于跨多个文件拆分模块的主要内容,如果未能解决你的问题,请参考以下文章
Maven -- 分模块开发与设计 & 分模块开发示例(拆分POJO & 拆分DAO)
Maven -- 分模块开发与设计 & 分模块开发示例(拆分SERVICE & 拆分CONTROLLER)