将模块拆分为多个文件

我想有一个模块,其中包含多个结构,每个人都有自己的档案。使用一个 Math模块作为例子:

Math/
Vector.rs
Matrix.rs
Complex.rs

我希望每个结构都在同一个模块中,我将从主文件中使用这个模块,如下所示:

use Math::Vector;


fn main() {
// ...
}

然而 Rust 的模块系统(一开始就有点混乱)并没有提供一个明显的方法来实现这一点。它似乎只允许您将整个模块放在一个文件中。这不土吗?如果没有,我该怎么做?

61304 次浏览

Rust 的模块系统实际上非常灵活,可以让您公开任何类型的结构,同时隐藏代码在文件中的结构。

我认为这里的关键是利用 pub use,它将允许您从其他模块重新导出标识符。这在 Rust 的 std::io板条箱中有先例,其中一些子模块的类型是 重新导出以供在 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![] }
}
}

从客户机的角度来看,VectorAVectorB在两个不同文件的两个不同模块中定义的事实是完全不透明的。

如果你和 main.rs在同一个目录下,你应该可以用以下命令运行它:

rustc src/lib.rs
rustc -L . main.rs
./main

总的来说,《锈》一书中的 “箱子和模块”一章非常好,有很多例子。

最后,Rust 编译器还会自动在子目录中为您查找。例如,上面的代码不会改变这个目录结构:

src/
lib.rs
vector/
mod.rs
vector_b.rs
main.rs

编译和运行的命令也保持不变。

好吧,与我的编译器斗争了一会儿,终于让它工作了(感谢 BurntSushi 指出了 pub use

主要目标:

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;

数学/向量

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...
}

其他结构也可以以同样的方式添加。

Rust 模块的规则是:

  1. 源文件 是正义的自己的模块(除了特殊文件 main.rs、 lib.rs 和 mod.rs 之外)。
  2. 目录 是正义的模块路径组件。
  3. 文件 mod.rs 是正义的目录的模块。

目录中的文件 Matrix. rs1的数学 是正义的模块 math::matrix。很简单。您在文件系统上看到的内容也可以在源代码中找到。这是文件路径和模块路径的双射。

因此,您可以使用 use math::matrix::Matrix导入一个 struct Matrix,因为 struct 位于目录计算中的文件 Matrix. rs 中。不开心?你更喜欢 use math::Matrix;,不是吗?有可能。使用以下命令重新导出 data/mod.rs 中的标识符 math::matrix::Matrix:

pub use self::math::Matrix;

还有另一个步骤可以让它运转起来。Rust 需要向模块 装弹声明一个模块。在主站中添加 mod math;。如果你不这样做,你会从编译器得到一个错误信息,当像这样导入:

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

这里的暗示有误导性。不需要额外的板条箱,当然,除非您真的打算编写一个单独的库。

在 main.rs 的顶部添加以下内容:

mod math;
pub use math::Matrix;

模块声明对于子模块 vectormatrixcomplex也是必需的,因为 math需要加载它们来重新导出它们。只有在加载了标识符的模块时,标识符的重新导出才有效。这意味着,要重新导出标识符 math::matrix::Matrix,您需要编写 mod matrix;。你可以在数学/mod.rs 中做到这一点。因此,创建包含以下内容的文件:

mod vector;
pub use self::vector::Vector;


mod matrix;
pub use self::matrix::Matrix;


mod complex;
pub use self::complex::Complex;

然后你就完了。


源文件名通常以 Rust 中的小写字母开头,这就是为什么我使用 Matrix. rs 而不是 Matrix.rs 的原因。

2 Java 的不同。也可以用 package声明路径。这是多余的。路径已经从文件系统中的源文件位置显而易见。为什么要在文件顶部的声明中重复这些信息?当然,有时候快速查看源代码比查找文件的文件系统位置更容易。我能理解那些说这样不那么让人困惑的人。

铁锈纯粹主义者可能会称我为异教徒,并且讨厌这个解决方案,但是这个方案要简单得多: 只要在自己的文件中完成每件事情,然后在 mod.rs 中使用“ 包括!”宏:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

这样就不会添加嵌套模块,避免了复杂的导出和重写规则。 简单,有效,不费吹灰之力。

我想在这里添加如何在 Rust 文件深度嵌套时包含它们。我有以下结构:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

如何从 main.rs访问 sink.rstoilet.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.")
}
  1. home目录中创建一个名为 bathroom.rs的文件:

  2. 导出文件名:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
  3. Create a file called home.rs next to main.rs

  4. pub mod the bathroom.rs file

    // home.rs
    pub mod bathroom;
    
  5. Within 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();
    }
    

Including other sibling modules (files) within submodules

In the case you'd like to use sink.rs from toilet.rs, you can call the module by specifying the self or super keywords.

// 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.rshome/bathroom.rs相同。Rust 做了这个修改,因为如果包含与目录同名的文件,编译器会感到困惑。2018版本(第一个显示的版本)修复了这种结构。

更多信息请参见 这个回购,总体说明请参见 YouTube 视频

最后一件事... 避免连字符! 使用 snake_case代替。

重要提示

你的 必须的桶所有的文件到顶部,即使深层文件不需要的顶级。

这意味着,为了让 sink.rs发现 toilet.rs,您需要使用上面的方法一直到 main.rs

换句话说,在 toilet.rs内做 pub mod sink;use self::sink;不是工作,除非你已经把它们一直暴露到 main.rs

因此,永远记住把你的文件放到顶端!

一个更多的沙沙声方法导出模块,这是我从 Github中学到的。

mod foo {
//! inner docstring comment 1
//! inner docstring comment 2


mod a;
mod b;


pub use a::*;
pub use b::*;
}