我想要一个包含多个结构的模块,每个都在自己的文件中。使用Math
模块作为示例:
Math/
Vector.rs
Matrix.rs
Complex.rs
我希望每个结构都在同一个模块中,我将从我的主文件中使用它,如下所示:
use Math::Vector;
fn main() {
// ...
}
然而,Rust的模块系统(开始时有点混乱)并没有提供一种明显的方法来实现这一点。它似乎只允许您将整个模块放在一个文件中。这不是质朴的吗?如果没有,我该怎么做?
答案 0 :(得分:91)
Rust的模块系统实际上非常灵活,可以让你暴露出你想要的任何类型的结构,同时隐藏你的代码在文件中的结构。
我认为这里的关键是使用pub use
,这将允许您从其他模块重新导出标识符。在Rust的std::io
箱子中有先例,其中子模块的某些类型是re-exported for use in std::io
。
为了适应您的示例,我们可以从这个目录结构开始:
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
编译和运行的命令也保持不变。
答案 1 :(得分:25)
Rust模块规则是:
目录math 中的文件matrix.rs 1 只是模块math::matrix
。这很容易。您在文件系统中看到的内容也可以在源代码中找到。这是文件路径和模块路径 2 的一对一对应关系。
因此,您可以使用Matrix
导入结构use math::matrix::Matrix
,因为结构位于目录math中的文件matrix.rs内。不开心?你非常喜欢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`?
提示在这里有误导性。除了你真的打算写一个单独的库之外,不需要额外的板条箱。
在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;
Aaaand你已经完成了。
1 源文件名通常以Rust中的小写字母开头。这就是我使用matrix.rs而不是Matrix.rs的原因。
2 Java与众不同。您也可以使用package
声明路径。这是多余的。从文件系统中的源文件位置开始,路径已经很明显。为什么要在文件顶部的声明中重复此信息?当然,有时可以更快地查看源代码而不是查找文件的文件系统位置。我能理解那些说它不那么令人困惑的人。
答案 2 :(得分:15)
好吧,和我的编译器争了一段时间,最后让它工作了(感谢BurntSushi指出pub use
。
main.rs:
use math::Vec2;
mod math;
fn main() {
let a = Vec2{x: 10.0, y: 10.0};
let b = Vec2{x: 20.0, y: 20.0};
}
数学/ mod.rs:
pub use self::vector::Vec2;
mod vector;
数学/ 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。
答案 3 :(得分:13)
Rusts纯粹主义者可能会称我为异教徒并讨厌这个解决方案,但这更简单:只需在自己的文件中执行每个操作,然后使用&#34; include!&#34; mod.rs中的宏:
include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");
这样您就不会添加嵌套模块,并避免复杂的导出和重写规则。 简单,有效,没有大惊小怪。
答案 4 :(得分:5)
我想在这里添加当深度嵌套时如何包含Rust文件。我有以下结构:
|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs
如何从sink.rs
访问toilet.rs
或main.rs
?
正如其他人所提到的,Rust不了解文件。相反,它将所有内容视为模块和子模块。要访问bathroom目录中的文件,您需要导出它们或将它们存储在顶部。为此,您可以通过指定文件名和要访问的目录,并在文件内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.")
}
在bathroom.rs
目录中创建一个名为home
的文件:
导出文件名:
// bathroom.rs
pub mod sink;
pub mod toilet;
在home.rs
旁边创建一个名为main.rs
的文件
pub mod
bathroom.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();
}
如果您要使用sink.rs
中的toilet.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
!
换句话说,在pub mod sink;
内进行use self::sink;
或toilet.rs
将不起作用,除非您一直将它们暴露到main.rs
!
因此,请始终记住将文件存储在顶部!
答案 5 :(得分:1)
一种更粗暴的导出模块的方法,这是我从 Github 中获取的。
mod foo {
//! inner docstring comment 1
//! inner docstring comment 2
mod a;
mod b;
pub use a::*;
pub use b::*;
}